版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
對(duì)于Android N以下,文件直接Uri.fromFile(file)就可以直接使用,Audroid N 即編譯app的版本 compileSdkVersion 24時(shí),此時(shí)會(huì)報(bào)出FileUriExposedException異常,解釋如下:
對(duì)于面向AndroidN 的應(yīng)用,Android框架執(zhí)行的 StrictMode,API 禁止向您的應(yīng)用外公開 file://URI。
如果一項(xiàng)包含文件 URI 的 Intent 離開您的應(yīng)用,應(yīng)用失敗,并出現(xiàn) FileUriExposedException異常。
若要在應(yīng)用間共享文件,您應(yīng)發(fā)送一項(xiàng) content://URI,并授予 URI 臨時(shí)訪問權(quán)限。
進(jìn)行此授權(quán)的最簡(jiǎn)單方式是使用 FileProvider類。
@Overridepublic voidonTakePhoto() {if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) {//7.0及其以后版本使用升級(jí)后的代碼處理Intent takePictureIntent =newIntent(MediaStore.ACTION_IMAGE_CAPTURE);if(takePictureIntent.resolveActivity(getPackageManager()) !=null) {//判斷是否有相機(jī)應(yīng)用picName= DateUtil.format(newDate(),"yyyyMMddHHmmss") +".jpg";File imagePath =newFile(Files.photoPath,picName);Uri photoURI =getUriForFile(this,"xxx.xxx.xxx",imagePath);takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//添加這一句表示對(duì)目標(biāo)應(yīng)用臨時(shí)授權(quán)該Uri所代表的文件takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,photoURI);startActivityForResult(takePictureIntent,PHOTO_REQUEST_TAKEPHOTO);}? ? }else{//7.0之前還保持原來方案進(jìn)行處理即可Intent cameraintent =newIntent(MediaStore.ACTION_IMAGE_CAPTURE);picName= DateUtil.format(newDate(),"yyyyMMddHHmmss") +".jpg";File f =newFile(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);}}
現(xiàn)在,需要配置FileProvider。在應(yīng)用程序的清單,提供者添加到您的應(yīng)用程序,authorities=”applicationId.fileprovider”,使用時(shí)
file-path
表示你應(yīng)用內(nèi)部存儲(chǔ)區(qū)域的文件的子目錄。這個(gè)子目錄和getFilesDir()的返回值一樣。external-path
表示你應(yīng)用外部存儲(chǔ)區(qū)域的文件的子目錄。這個(gè)子目錄和getExternalFilesDir()的返回值一樣。cache-path
表示你應(yīng)用內(nèi)部存儲(chǔ)區(qū)域的緩存子目錄。這個(gè)子目錄的根目錄和getCacheDir()的返回值一樣。(如果你修改了provider和paths中的值,需要把應(yīng)用卸載重裝或者開關(guān)機(jī)一下才能看到變化。)
@Overrideprotected voidonActivityResult(intrequestCode, intresultCode,Intent data) {super.onActivityResult(requestCode,resultCode,data);switch(requestCode) {casePHOTO_REQUEST_TAKEPHOTO:// 當(dāng)選擇拍照時(shí)調(diào)用if(resultCode ==RESULT_CANCELED) {return;}else if(resultCode !=RESULT_OK) {? ? ? ? ? ? ? ? showMsg("Take photo failed.");}else{? ? ? ? ? ? ? //處理返回?cái)?shù)據(jù)}break;
}
}
FileProvider 是 ContentProvider 的一個(gè)特殊的子類,它有利于安全地分享應(yīng)用相關(guān)的文件,通過對(duì)一個(gè)文件創(chuàng)建content:// Uri而不是file:/// Uri。
由于FileProvider的默認(rèn)功能包括文件的content URI的生成,你并不需要在代碼中定義一個(gè)子類。相反,你可以在你的應(yīng)用中包含一個(gè)FileProvider通過在XML文件中指定它。對(duì)于指定FileProvider,添加一個(gè)元素在你應(yīng)用的清單文件中。設(shè)置android:name屬性為android.support.v4.content.FileProvider。根據(jù)你控制的域名設(shè)置android:authorities屬性為一個(gè)URI authority(authorities可以隨意填寫,但是要保證使用時(shí)與authority保持一致,推薦applicationId.fileprovider,以免定義重復(fù))。設(shè)置android:exported屬性為false;FileProvider不需要公開。設(shè)置android:grantUriPermissions屬性為true,為了允許你進(jìn)行臨時(shí)訪問文件的授權(quán)。
一個(gè)FileProvider只能生成一個(gè)content
URI
對(duì)應(yīng)你事先指定目錄下的文件。對(duì)于指定一個(gè)目錄,使用元素的子元素,在XML中指定它的存儲(chǔ)區(qū)域和路徑。例如,下面的paths元素告訴FileProvider你打算請(qǐng)求你的私有文件區(qū)域的
images/ 子目錄的content URIs
以下摘自Androiddeveloper 文檔:
在較早的 Android 版本中,您的應(yīng)用可以使用存儲(chǔ)訪問框架來允許用戶從他們的云存儲(chǔ)帳戶中選擇文件,如 Google Drive。但是,不能表示沒有直接字節(jié)碼表示的文件;每個(gè)文件都必須提供一個(gè)輸入流。
Android 7.0 在存儲(chǔ)訪問框架中添加了虛擬文件的概念。虛擬文件功能可以讓您的DocumentsProvider返回可與ACTION_VIEWintent
使用的文件 URI,即使它們沒有直接字節(jié)碼表示。Android 7.0 還允許您為用戶文件(虛擬或其他類)提供備用格式。
為獲得您的應(yīng)用中的虛擬文件的 URI,首先您應(yīng)創(chuàng)建一個(gè)Intent以打開文件選擇器 UI。由于應(yīng)用不能使用openInputStream()方法來直接打開一個(gè)虛擬文件,因此如果您包括了CATEGORY_OPENABLE類別,您的應(yīng)用不會(huì)收到任何虛擬文件。
在用戶選擇之后,系統(tǒng)調(diào)用onActivityResult()方法。您的應(yīng)用可以檢索虛擬文件的 URI,并得到一個(gè)輸入流,這表現(xiàn)在以下片段中的代碼。
// Other Activity code ...finalstaticprivateintREQUEST_CODE=64;// We listen to the OnActivityResult event to respond to the user's selection.@OverridepublicvoidonActivityResult(intrequestCode,intresultCode,IntentresultData){try{if(requestCode==REQUEST_CODE&&resultCode==Activity.RESULT_OK){Uriuri=null;if(resultData!=null){uri=resultData.getData();ContentResolverresolver=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,"*/*");AssetFileDescriptordescriptor=resolver.openTypedAssetFileDescriptor(uri,streamTypes[0],null);// Retrieve a stream to the virtual file.InputStreaminputStream=descriptor.createInputStream();}}}catch(Exceptionex){Log.e("EXCEPTION","ERROR: ",ex);}}