Android 調起系統相機拍照

概述

最近在看 nanChen 寫的圖片選擇器 ImagePicker,感受寫得很不錯,也打算把從中學到的東西寫下來。不少時候,遇到一個好的框架可以下降開發成本這是好事。可是也要去了解其內部具體實現邏輯,說不定哪天你須要完成一個相似的小功能,你知道原理就能快速寫出來,而不是引入整個框架。android

本文講其中的第一個功能:如何調起手機的相機拍照?app

系統現有相機應用

對於如何調用系統現有應用,這裏簡單再說一下。在開發的應用中調用系統現有應用,須要使用 Intent 指定開啓的應用的 Action 和 Category,而後經過 startActivity(Intent) 或者 startActivityForResult(Intent, int) 開啓指定的 Activity,若是使用 startActivityForResult() 方法開啓並須要返回值,再重寫 onActivityResult(int, int, Intent) 便可。框架

先來看看系統現有相機應用的 AndroidManifest.xml 清單文件定義的 Activity:ide

        <activity
            android:name="com.android.camera.Camera"
            android:clearTaskOnLaunch="true"
            android:configChanges="orientation|keyboardHidden"
            android:screenOrientation="landscape"
            android:taskAffinity="android.task.camera"
            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <categroy android:name="android.intent.category.DEFAULT" />
                <categroy android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.media.action.IMAGE_CAPTURE" />
                <categroy android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.media.action.STILL_IMAGE_CAMERA" />
                <categroy android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.android.camera.VideoCamera"
            android:clearTaskOnLaunch="true"
            android:configChanges="origientation|keyboardHidden"
            android:label="@string/video_camera_label"
            android:screenOrientation="landscape"
            android:taskAffinity="android.task.camcorder"
            android:theme="@android:style/theme.Black.NoTitleBar.Fullscreen" >
            <intent-filter>
                <action android:name="android.media.action.VIDEO_CAMERA" />
                <categroy android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.media.action.VIDEO_CAPTURE" />
                <categroy android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>    

 它定義了兩個 Activity,com.android.camera.Camera 表示照相機,com.android.camera.VideoCamera 表示攝像機。從字面意思能夠看出,爲了捕獲系統相機返回的數據,通常須要使用一下兩個 Action 便可開啓照相機與攝像機:函數

  • android.media.action.IMAGE_CAPTURE:Intent 的 Action 類型,從現有的相機應用中請求一張圖片。this

  • android.media.action.VIDEO_CAPTURE:Intent 的 Action 類型,從現有的相機應用中請求一段視頻。spa

上面兩個參數,均在 MediaStore 類中以靜態常量的形式定義好了,分別是:MediaStore.ACTION_IMAGE_CAPTURE (相機) 和 MediaStore.ACTION_VIDEO_CAPTURE (攝像機)。code

獲取系統現有相機拍攝的圖片

在新開啓的 Activity 中,若是須要獲取它的返回值,則須要使用 startActivityForResult(Intent,int) 方法打開 Activity,並重寫 onActivityResult(int, int, Intent) 獲取系統相機的返回數據,那麼咱們只須要在 onActivityResult() 中獲取到返回值便可。視頻

系統相機拍攝的照片,若是不指定路徑,會保存在系統默認文件夾下,可使用 Intent.getExtra() 方法獲得,獲得的是一個 Uri 地址,表示了一個內容提供者的地址。若是經過MediaStore.EXTRA_OUTPUT 指定了保存路徑,那麼經過 Intent.getExtra() 獲得的將是一個空地址,可是既然是咱們指定的地址,那麼也不愁找不到它了。xml

可是若是是7.0以上,須要使用 FileProvider 來獲得這個 uri 地址。

實現方案

說清楚流程以後,下面就是具體代碼實現:

首先是在 AndroidManiFest.xml 下聲明拍照權限:

<uses-permission android:name="android.permission.CAMERA" />

聲明權限後,在開始拍照前,仍是須要判斷用戶是否給了咱們拍照的權限:

if (( mActivity).checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.CAMERA}, GalleryActivity.REQUEST_PERMISSION_CAMERA);
} else {
  imagePicker.takePicture(mActivity, GalleryActivity.REQUEST_CODE_TAKE);
}

若是用戶沒有給權限,那麼須要申請權限,權限申請之後,會有一個回調通知開發者是否容許了,具體見下發的代碼:

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSION_CAMERA) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                imagePicker.takePicture(this, REQUEST_CODE_TAKE);
            } else {
                showToast("權限被禁止,沒法打開相機");
            }
        }
    }

權限容許以後,經過 imagePicker.takePicture 去拍照。下面看下拍照的具體代碼邏輯:

    /**
     * 拍照的方法
     */
    public void takePicture(Activity activity, int requestCode) {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        takePictureIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
            if (Utils.existSDCard()) takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
            else takeImageFile = Environment.getDataDirectory();
            takeImageFile = createFile(takeImageFile, "IMG_", ".jpg");
            if (takeImageFile != null) {
                // 默認狀況下,即不須要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                // 照相機有本身默認的存儲路徑,拍攝的照片將返回一個縮略圖。若是想訪問原始圖片,
                // 能夠經過dat extra可以獲得原始圖片位置。即,若是指定了目標uri,data就沒有數據,
                // 若是沒有指定uri,則data就返回有數據!

                Uri uri;
                if (VERSION.SDK_INT <= VERSION_CODES.M) {
                    uri = Uri.fromFile(takeImageFile);
                } else {

                    /**
                     * 7.0 調用系統相機拍照再也不容許使用Uri方式,應該替換爲FileProvider
                     * 而且這樣能夠解決MIUI系統上拍照返回size爲0的狀況
                     */
                    uri = FileProvider.getUriForFile(activity, "com.example.myapplication.provider", takeImageFile);
                    //加入uri權限 要不三星手機不能拍照
                    List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                    for (ResolveInfo resolveInfo : resInfoList) {
                        String packageName = resolveInfo.activityInfo.packageName;
                        activity.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    }
                }

              //  Log.e("nanchen", ProviderUtil.getFileProviderName(activity));
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            }
        }
        activity.startActivityForResult(takePictureIntent, requestCode);
    }

關於 Intent.resolveActivity 做用,簡單來講就是當你在調用第三方軟件或者系統 Activity,相似打開相機,發送圖片等隱式 Intent,是並不必定可以在全部的 Android 設備上都正常運行。經過該方法判斷這個 intent 對應的 activity 是否存在,確保不會出現崩潰。

takeImageFile 是圖片存儲地址,在7.0之前,須要用 Uri.fromFile 進行處理。7.0以後的採用 FileProvider。下面介紹下 FileProvider 的使用方法:

註冊

使用 FileProvider 須要在 AndroidManiFest.xml 裏聲明:

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapplication.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        </provider> 

這裏是直接使用的 v4 包中的 FileProvider,咱們也能夠直接繼承 FileProvider 類,適當重寫重載函數,但不建議如此作。下面來介紹上面的幾個設置:

  • name: provider 的類名,若使用默認的 v4 的 FileProvider 可以使用 "android.support.v4.content.FileProvider",也能夠設置爲自定義的繼承 FileProvider 的 provider 類;

  • authorities: 一個簽名認證,能夠自定義,但在獲取 uri 的時候須要保持一致;

  • grantUriPermissions: 使用 FileProvider 的使用須要咱們給流出的URI 賦予臨時訪問權限(READ 和 WRITE),該設置是容許咱們行使該項權力;

  • meta-data: meta-data 配置的是咱們能夠訪問的文件的路徑配置信息,須要使用 xml 文件進行配置,FileProvider 會經過解析 xml 文件獲取配置項,其中 name 名字不可改變爲: android.support.FILE_PROVIDER_PATHS,resource 爲配置路徑信息的配置項目。

路徑配置

可訪問的路徑配置能夠在 res 中創建一個 xml 文件下面創建一個配置文件,格式以下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <!--path:須要臨時受權訪問的路徑(.表明全部路徑)-->
  <!--name:就是你給這個訪問路徑起個名字-->
  <external-path name="cam" path="." />
</paths>

下面解釋下:

  • <files-path/>   表明的根目錄: Context.getFilesDir()

  • <external-path/>   表明的根目錄: Environment.getExternalStorageDirectory()
  • <cache-path/>  表明的根目錄: getCacheDir()

最後,將 uri 放到 MediaStore.EXTRA_OUTPUT 中。

takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

拍照完成後,會回調 onActivityResult,在這裏咱們能夠根據先前傳的值將圖片展現到 ImageView 中:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.i(TAG, "系統相機拍照完成,resultCode=" + resultCode + " " + requestCode);
        if (requestCode == REQUEST_CODE_TAKE) {
            Uri uri = Uri.fromFile(takeImageFile);
            iv_CameraImg.setImageURI(uri);
        } 
    }

到此,調用系統相機拍照的過程到此就結束了。

相關文章
相關標籤/搜索