快速使用FileProvider解決Android7.0檔案許可權問題
升級到Android7.0之後,啟動系統相機或者截圖,傳入URI的時候可能會導致程式閃退崩潰。這是因為7.0的新的檔案許可權導致的。下面是解決這個問題的快速解決方案。
問題程式碼
在7.0可能會出問題的程式碼:
final String CACHE_IMG = Environment.getExternalStorageDirectory()+"/demo/"
final int TAG_PHOTO_CAMERA=200;
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String fileName = "defaultImage.jpg" ;
File file = new File(CACHE_IMG, fileName);
Uri uri = Uri.fromFile(file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, TAG_PHOTO_CAMERA);
其中Uri uri = Uri.fromFile(file);這裡會導致閃退。
解決方法
step1. 將Uri的生成方式改為由FileProvider提供的臨時授權路徑,並且在intent中新增flag
修改後程式碼如下
final String CACHE_IMG = Environment.getExternalStorageDirectory()+"/demo/"
final int TAG_PHOTO_CAMERA=200;
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String fileName = "defaultImage.jpg";
File file = new File(CACHE_IMG, fileName);
Uri imageUri=FileProvider.getUriForFile(activity,"me.xifengwanzhao.fileprovider" , file);//這裡進行替換uri的獲得方式
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//這裡加入flag
startActivityForResult(intent, TAG_PHOTO_CAMERA);
step2.在AndroidManifest.xml中的application標籤中新增provider的配置
<application
...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="me.xifengwanzhao.fileprovider"//這裡需要和上面部分字串相同
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
step3.在res/xml中新建一個檔案file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<resource xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="images"
path="demo/" />
</resource>
OK,大功告成,這樣就不會崩潰了
程式碼解釋
對於面向 Android 7.0 的應用,Android 框架執行的 StrictMode API 政策禁止在您的應用外部公開 file:// URI。如果一項包含檔案 URI 的 intent 離開您的應用,則應用出現故障,並出現 FileUriExposedException 異常。
要在應用間共享檔案,您應傳送一項 content:// URI,並授予 URI 臨時訪問許可權。進行此授權的最簡單方式是使用 FileProvider 類。如需瞭解有關許可權和共享檔案的詳細資訊,請參閱共享檔案。
根據文件提示我們使用FileProvider進行處理,同時利用xml對FileProvider進行配置
參考如下
java根路徑產生方式 | 對應xml根節點名稱 |
---|---|
Context.getFilesDir() | files-path |
getCacheDir() | cache-path |
Environment.getExternalStorageDirectory() | external-path |
Context#getExternalFilesDir(String) Context.getExternalFilesDir(null) | external-files-path |
Context.getExternalCacheDir() | external-cache-path |
節點中的name 不可重名,path為自定義
關於相簿選圖和相機裁剪
有同學反映相簿選圖和相機裁剪時候的報錯問題,這裡也說一下
系統相簿選圖返回的Uri是可以直接使用的,不需要也不能使用FileProvider進行轉換
如果需要根據uri獲得轉換後的uri 可以參考如下方式
Uri fromUri;
if (uri.getScheme() != null && uri.getScheme().startsWith("file")) {
fromUri =
FileProvider.getUriForFile(mContext,"me.xifengwanzhao.fileprovider", new File(FileUtils.getPath(mContext, uri)));//這裡進行替換uri的獲得方式
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//這裡加入flag
} else {
//相簿選圖適配
fromUri = uri;
}
關於相機裁剪
相機裁剪 intent.setDataAndType(fromUri, “image/*”);這裡是需要對uri進行轉換的,
而 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));這裡使用原來的方式獲取uri就可以了
那麼啟動系統裁剪的方法可以寫成這樣
/**
* 開啟截圖,啟動系統的截圖方法 返回requestCode為 {Constant.IMG_ZOOM}
*
* @param mContext 必須為activity
* @param uri 需要進行裁剪的圖片的uri
* @param size 截圖的大小寬和高的數值,這裡僅限截圖為1:1的正方形
* @return path 截圖返回的路徑
* @see Constant#IMG_ZOOM
*/
public static String startPhotoZoom(Activity mContext, Uri uri, int size) {
//這裡生成一個儲存截圖用的臨時路徑並且返回出去
String imgPath;
File file = new File(Constant.ZOOM_IMAGE, Constant.getNewestImageName(mContext));
imgPath = file.getPath();
Intent intent = new Intent("com.android.camera.action.CROP");
Uri fromUri;
if (uri.getScheme() != null && uri.getScheme().startsWith("file")) {
fromUri = FileProvider.getUriForFile(mContext, "me.xifengwanzhao.fileprovider", new File(FileUtils.getPath(mContext, uri)));//這裡進行替換uri的獲得方式
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//這裡加入flag
} else {
//相簿選圖適配
fromUri = uri;
}
intent.setDataAndType(fromUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
if (android.os.Build.MANUFACTURER.contains("HUAWEI")) {// 華為特殊處理 不然會顯示圓
intent.putExtra("aspectX", 9998);
intent.putExtra("aspectY", 9999);
} else {
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
}
intent.putExtra("outputX", size);
intent.putExtra("outputY", size);
mContext.startActivityForResult(intent, Constant.IMG_ZOOM);
return imgPath;
}