版權(quán)聲明:本文為博主原創(chuàng)文章绵载,未經(jīng)博主允許不得轉(zhuǎn)載饲梭。
對于Android N以下,文件直接Uri.fromFile(file)就可以直接使用早歇,Audroid N 即編譯app的版本 compileSdkVersion 24時倾芝,此時會報出FileUriExposedException異常,解釋如下:
對于面向AndroidN 的應用箭跳,Android框架執(zhí)行的 StrictMode晨另,API 禁止向您的應用外公開 file://URI。
如果一項包含文件 URI 的 Intent 離開您的應用谱姓,應用失敗借尿,并出現(xiàn) FileUriExposedException異常。
若要在應用間共享文件屉来,您應發(fā)送一項 content://URI路翻,并授予 URI 臨時訪問權(quán)限。
進行此授權(quán)的最簡單方式是使用 FileProvider類奶躯。
@Overridepublic voidonTakePhoto() {if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) {//7.0及其以后版本使用升級后的代碼處理Intent takePictureIntent =newIntent(MediaStore.ACTION_IMAGE_CAPTURE);if(takePictureIntent.resolveActivity(getPackageManager()) !=null) {//判斷是否有相機應用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);//添加這一句表示對目標應用臨時授權(quán)該Uri所代表的文件takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,photoURI);startActivityForResult(takePictureIntent,PHOTO_REQUEST_TAKEPHOTO);}? ? }else{//7.0之前還保持原來方案進行處理即可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。在應用程序的清單嘹黔,提供者添加到您的應用程序账嚎,authorities=”applicationId.fileprovider”,使用時
file-path
表示你應用內(nèi)部存儲區(qū)域的文件的子目錄儡蔓。這個子目錄和getFilesDir()的返回值一樣郭蕉。external-path
表示你應用外部存儲區(qū)域的文件的子目錄。這個子目錄和getExternalFilesDir()的返回值一樣喂江。cache-path
表示你應用內(nèi)部存儲區(qū)域的緩存子目錄召锈。這個子目錄的根目錄和getCacheDir()的返回值一樣。(如果你修改了provider和paths中的值获询,需要把應用卸載重裝或者開關(guān)機一下才能看到變化涨岁。)
@Overrideprotected voidonActivityResult(intrequestCode, intresultCode,Intent data) {super.onActivityResult(requestCode,resultCode,data);switch(requestCode) {casePHOTO_REQUEST_TAKEPHOTO:// 當選擇拍照時調(diào)用if(resultCode ==RESULT_CANCELED) {return;}else if(resultCode !=RESULT_OK) {? ? ? ? ? ? ? ? showMsg("Take photo failed.");}else{? ? ? ? ? ? ? //處理返回數(shù)據(jù)}break;
}
}
FileProvider 是 ContentProvider 的一個特殊的子類拐袜,它有利于安全地分享應用相關(guān)的文件,通過對一個文件創(chuàng)建content:// Uri而不是file:/// Uri梢薪。
由于FileProvider的默認功能包括文件的content URI的生成蹬铺,你并不需要在代碼中定義一個子類。相反秉撇,你可以在你的應用中包含一個FileProvider通過在XML文件中指定它甜攀。對于指定FileProvider,添加一個元素在你應用的清單文件中琐馆。設(shè)置android:name屬性為android.support.v4.content.FileProvider规阀。根據(jù)你控制的域名設(shè)置android:authorities屬性為一個URI authority(authorities可以隨意填寫,但是要保證使用時與authority保持一致瘦麸,推薦applicationId.fileprovider谁撼,以免定義重復)。設(shè)置android:exported屬性為false瞎暑;FileProvider不需要公開彤敛。設(shè)置android:grantUriPermissions屬性為true,為了允許你進行臨時訪問文件的授權(quán)了赌。
一個FileProvider只能生成一個content
URI
對應你事先指定目錄下的文件墨榄。對于指定一個目錄,使用元素的子元素勿她,在XML中指定它的存儲區(qū)域和路徑袄秩。例如,下面的paths元素告訴FileProvider你打算請求你的私有文件區(qū)域的
images/ 子目錄的content URIs
以下摘自Androiddeveloper 文檔:
在較早的 Android 版本中逢并,您的應用可以使用存儲訪問框架來允許用戶從他們的云存儲帳戶中選擇文件之剧,如 Google Drive。但是砍聊,不能表示沒有直接字節(jié)碼表示的文件背稼;每個文件都必須提供一個輸入流。
Android 7.0 在存儲訪問框架中添加了虛擬文件的概念玻蝌。虛擬文件功能可以讓您的DocumentsProvider返回可與ACTION_VIEWintent
使用的文件 URI蟹肘,即使它們沒有直接字節(jié)碼表示。Android 7.0 還允許您為用戶文件(虛擬或其他類)提供備用格式俯树。
為獲得您的應用中的虛擬文件的 URI帘腹,首先您應創(chuàng)建一個Intent以打開文件選擇器 UI。由于應用不能使用openInputStream()方法來直接打開一個虛擬文件许饿,因此如果您包括了CATEGORY_OPENABLE類別阳欲,您的應用不會收到任何虛擬文件。
在用戶選擇之后,系統(tǒng)調(diào)用onActivityResult()方法球化。您的應用可以檢索虛擬文件的 URI秽晚,并得到一個輸入流,這表現(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);}}