Android 7.0呼叫系統相機(檔案訪問crash android.os.FileUriExposedException)
最近專案中做圖片上傳的功能中遇到一個問題,就是7.0的手機上呼叫系統相機指定圖片路徑的情況下回crash,報錯android.os.FileUriExposedException uri暴露的錯誤。
Android7.0對應用共享檔案這塊做了一些強制性的要求。從7.0開始,android框架預設執行StrictMode,禁止嚮應用外公開file://的URI,也就是說必須將此URI轉換為content://,才能給別的應用使用,並授予URI臨時訪問許可權。
知道錯誤原因了,那麼解決辦法就好說了:
第一種辦法:繞過7.0的檔案許可權檢查
既然預設執行了StrictMode,那最簡單的辦法就是禁止執行StrictMode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
}
這段程式碼可以寫在Application的onCreate()裡,也可以寫在需要呼叫檔案訪問的地方。 當然這是一種比較流氓的方法,放棄了設計者的初衷;
第二種辦法:使用 FileProvider類授權
使用方法:
1.在manifest.xml中新增provider
<application
...>
<provider
android:authorities="你的包名.fileProvider"
android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
2.在res資料夾下新建資料夾xml,在xml裡新建檔案file_paths.xml,
file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/包名/" name="files_root" />
<external-path path="." name="external_storage_root" />
<cache-path name="cache_paths" path="cache/"/>
</paths>
files-path代表的根目錄: Context.getFilesDir()
external-path代表的根目錄: Environment.getExternalStorageDirectory()
cache-path代表的根目錄: getCacheDir()
path路徑當然也可以自己定義,與實際檔案路徑存在就行;“.”類似於萬用字元,使用所有的路徑
例如檔案路徑“/storage/emulated/0/Android/data/包名/cache/1512556077575.jpg”
7.0的URI:”content://包名.fileProvider/files_root/cache/1512556077575.jpg”
其中“包名.fileProvider”為在manifest.xml中配置的authorities。
3.在呼叫相機時:
private void openCamera() {
String photoName = Calendar.getInstance().getTimeInMillis() + ".jpg";
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
file = new File(getExternalCacheDir(), photoName);//file是一個全域性變數
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//7.0 以上
picUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileProvider", file);
}else {
//7.0 以下
picUri = Uri.fromFile(file);
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri);
startActivityForResult(intent, CAMERA_REQUEST);
}
其中FileProvider.getUriForFile()的第二個引數必須與在在manifest.xml中配置的authorities一致,也可以不使用包名,但必須保持一致。
最終7.0上:
file:/storage/emulated/0/Android/data/包名/cache/1512556077575.jpg
picUri :content://包名.fileProvider/files_root/cache/1512556077575.jpg
然後就可以正常開啟相機了。
4.拍照返回
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode){
case CAMERA_REQUEST://拍照返回
ivPic.setImageURI(picUri);//顯示圖片
//以下程式碼用於獲取圖片檔案,7.0如果也用picUri.getPath(),則得到的檔案f為空,所以通過全域性變數file來獲取
String url = picUri.getPath();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
url = file.getPath();
}
File f = new File(url);
System.out.println("~file bytes = " + f.length() + " b");
//todo
break;
case ALNUM_REQUEST:
break;
}
}
關於6.0許可權問題不要忘了,就不在此列出了。
此文章借鑑很多他人部落格,不在一一列舉,謝謝各位。