1. 程式人生 > >Android 7.0呼叫系統相機適配筆記

Android 7.0呼叫系統相機適配筆記

對於Android N以下,檔案直接Uri.fromFile(file)就可以直接使用,Audroid N 即編譯app的版本 compileSdkVersion 24時,此時會報出FileUriExposedException異常,解釋如下:

對於面向 Android N 的應用,Android 框架執行的 StrictMode,API 禁止向您的應用外公開 file://URI。 
如果一項包含檔案 URI 的 Intent 離開您的應用,應用失敗,並出現 FileUriExposedException異常。

若要在應用間共享檔案,您應傳送一項 content://URI,並授予 URI 臨時訪問許可權。 
進行此授權的最簡單方式是使用 FileProvider類。

@Override
public void onTakePhoto() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        //7.0及其以後版本使用升級後的程式碼處理
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {//判斷是否有相機應用
picName = DateUtil.format
(new Date(), "yyyyMMddHHmmss") + ".jpg"; File imagePath = new File(Files.photoPath, picName); Uri photoURI = getUriForFile(this, "xxx.xxx.xxx", imagePath); takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //新增這一句表示對目標應用臨時授權該Uri所代表的檔案 takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT
, photoURI); startActivityForResult(takePictureIntent, PHOTO_REQUEST_TAKEPHOTO); } } else { //7.0之前還保持原來方案進行處理即可 Intent cameraintent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); picName = DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".jpg"; File f = new File(Files.photoPath, picName); cameraintent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f)); PrefTool.setBooleanSave(this, Prefs.PRE_NOT_TO_BACKGROUD, true); startActivityForResult(cameraintent, PHOTO_REQUEST_TAKEPHOTO); } }
現在,需要配置FileProvider。在應用程式的清單,提供者新增到您的應用程式,authorities=”applicationId.fileprovider”,使用時
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="xxx.xxx.xxx"
android:exported="false"
android:grantUriPermissions="true">
    <meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
file-path 表示你應用內部儲存區域的檔案的子目錄。這個子目錄和getFilesDir()的返回值一樣。external-path 表示你應用外部儲存區域的檔案的子目錄。這個子目錄和getExternalFilesDir()的返回值一樣。cache-path 表示你應用內部儲存區域的快取子目錄。這個子目錄的根目錄和getCacheDir()的返回值一樣。(如果你修改了provider和paths中的值,需要把應用解除安裝重灌或者開關機一下才能看到變化。)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);    switch (requestCode) {
        case PHOTO_REQUEST_TAKEPHOTO:// 當選擇拍照時呼叫
if (resultCode == RESULT_CANCELED) {
                return;
} else if (resultCode != RESULT_OK) {
                showMsg("Take photo failed.");
} else {
              //處理返回資料}
            break;
}
}

關於FileProvider

FileProvider 是 ContentProvider 的一個特殊的子類,它有利於安全地分享應用相關的檔案,通過對一個檔案建立content:// Uri而不是file:/// Uri。

由於FileProvider的預設功能包括檔案的content URI的生成,你並不需要在程式碼中定義一個子類。相反,你可以在你的應用中包含一個FileProvider通過在XML檔案中指定它。對於指定FileProvider,新增一個元素在你應用的清單檔案中。設定android:name屬性為android.support.v4.content.FileProvider。根據你控制的域名設定android:authorities屬性為一個URI authority(authorities可以隨意填寫,但是要保證使用時與authority保持一致,推薦applicationId.fileprovider,以免定義重複)。設定android:exported屬性為false;FileProvider不需要公開。設定android:grantUriPermissions屬性為true,為了允許你進行臨時訪問檔案的授權。

一個FileProvider只能生成一個content URI 對應你事先指定目錄下的檔案。對於指定一個目錄,使用元素的子元素,在XML中指定它的儲存區域和路徑。例如,下面的paths元素告訴FileProvider你打算請求你的私有檔案區域的 images/ 子目錄的content URIs
<files-path
name="name"
path="path"/> <!--相當 Context.getFilesDir() + path, name是分享url的一部分-->
<cache-path
name="name"
path="path"/> <!--getCacheDir()-->
<external-path
name="name"
path="path"/> <!--Environment.getExternalStorageDirectory()-->
<external-files-path
name="name"
path="path"/><!--getExternalFilesDir(String) Context.getExternalFilesDir(null)-->
<external-cache-path
name="name"
path="path"/> <!--Context.getExternalCacheDir()-->

以下摘自Android developer 文件:

虛擬檔案

在較早的 Android 版本中,您的應用可以使用儲存訪問框架來允許使用者從他們的雲端儲存帳戶中選擇檔案,如 Google Drive。但是,不能表示沒有直接位元組碼錶示的檔案;每個檔案都必須提供一個輸入流。

Android 7.0 在儲存訪問框架中添加了虛擬檔案的概念。虛擬檔案功能可以讓您的  返回可與  intent 使用的檔案 URI,即使它們沒有直接位元組碼錶示。Android 7.0 還允許您為使用者檔案(虛擬或其他類)提供備用格式。

為獲得您的應用中的虛擬檔案的 URI,首先您應建立一個  以開啟檔案選擇器 UI。由於應用不能使用  方法來直接開啟一個虛擬檔案,因此如果您包括了  類別,您的應用不會收到任何虛擬檔案。

在使用者選擇之後,系統呼叫  方法。您的應用可以檢索虛擬檔案的 URI,並得到一個輸入流,這表現在以下片段中的程式碼。

// Other Activity code ...finalstaticprivateint REQUEST_CODE =64;// We listen to the OnActivityResult event to respond to the user's selection.@Overridepublicvoid onActivityResult(int requestCode,int resultCode,Intent resultData){try{if(requestCode == REQUEST_CODE &&
            resultCode ==Activity.RESULT_OK){Uri uri =null;if(resultData !=null){
                uri = resultData.getData();ContentResolver resolver = getContentResolver();// Before attempting to coerce a file into a MIME type,// check to see what alternative MIME types are available to// coerce this file into.String[] streamTypes =
                  resolver.getStreamTypes(uri,"*/*");AssetFileDescriptor descriptor =
                    resolver.openTypedAssetFileDescriptor(
                        uri,
                        streamTypes[0],null);// Retrieve a stream to the virtual file.InputStream inputStream = descriptor.createInputStream();}}}catch(Exception ex){Log.e("EXCEPTION","ERROR: ", ex);}}

如需瞭解有關訪問使用者檔案的詳細資訊,請參閱儲存訪問框架指南