背景:
SamSung SM-N9006 Android5.0在應用中拍照之后甥材,無法獲取拍照之后的數據谤职,報錯FileUriExposedException
思路:
參考官方文檔對該錯誤的解釋,是由于出于安全考慮,Android 7.0[API24]以及以上版本不支持file://,使用content://URI,可能三星這款機型動了Framework吧谬泌。
Note: We are using getUriForFile(Context, String, File) which returns a content:// URI. For more recent apps targeting Android 7.0 (API level 24) and higher, passing a file:// URI across a package boundary causes a FileUriExposedException. Therefore, we now present a more generic way of storing images using a FileProvider.
關鍵方法:
1.啟動照相時,借助FileProvider生成content://URI保存拍照結果
老方法
/**
* 老方法[Android7.0以及以上報錯FileUriExposedException]
*/
private void doTakePhotoOld() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getPackageManager()) != null) {
File newFile = createTakePhotoFile();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(newFile));
startActivityForResult(intent, REQUEST_CAMERA);
}
}
新方法
/**
* 解決三星手機拍照后無法獲取數據逻谦,android 7.0手機拍照后獲取數據適配呵萨。
* 報錯FileUriExposedException(SamSung SM-N9006 Android5.0也有這問題)
*/
private void doTakePhoto() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager())!=null){
midFile = null;
try {
midFile = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
if (midFile!=null){
Uri photoURI = FileProvider.getUriForFile(this,FILE_AUTHORITY,midFile);
// fix java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{42725078 24872:com.android.camera/u0a14} (pid=24872, uid=10014) that is not exported from uid 10310
List<ResolveInfo> resInfoList= getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, photoURI,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
// fix java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{42725078 24872:com.android.camera/u0a14} (pid=24872, uid=10014) that is not exported from uid 10310
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,photoURI);
this.startActivityForResult(takePictureIntent, GetByCamre);
}
}
}
/**
*storage/emulated/0/.REAPP/._Image:SDCard路徑加上.REAPP/._Image/temp.img
* @return 接收拍照返回數據位置,
* @throws IOException
*/
private File createImageFile() throws IOException {
File dir = new File(SuFileInfo.getInstance().getImagePath());
if (!dir.exists()){
dir.mkdirs();
}
// 以上為了規(guī)避用戶刪除了._Image目錄,報錯java.io.IOException: open failed: ENOENT (No such file or directory)
File file = new File(dir, TempImageMame);
if (!file.exists()){
file.createNewFile();
}
return file;
}
小米復現java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{42725078 24872:com.android.camera/u0a14} (pid=24872, uid=10014) that is not exported from uid 10310跨跨,此時需要for循環(huán)授權進行修復潮峦。
2.對FileProvider進行設置
2.1AndroidManifest.xml注冊
<application ...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.harry.shopping.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
...
</application>
注意,android:authorities屬性值和之前FileProvider.getUriForFile方法使用的authorities必須保持一致勇婴。
2.2在res/xml新建file_paths.xml設置文件路徑
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="images" path="Android/data/com.harry.shopping/files/Pictures" />
</paths>
經過以上操作就可以在onActivityResult里面獲取到照片路徑mCurrentPhotoPath忱嘹。
附錄1:使用時FileProvider五個步驟
1.定義一個FileProvider,并在AndroidManifest.xml注冊耕渴。一般v4包下的的FileProvider即可
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.harry.shopping.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
2.Provider配置文件路徑
2.1配置meta-data指定保存文件路徑
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
2.2在xml文件下新建file_paths配置路徑
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="images" path="Android/data/com.harry.shopping/files/Pictures" />
</paths>
name表示生成URI時的別名拘悦,path是指相對路徑
<files-path name="name" path="path" />
Context.getFilesDir()
<cache-path name="name" path="path" />
Context.getCacheDir()
<external-path name="name" path="path" />
Environment.getExternalStorageDirectory()
<external-files-path name="name" path="path" />
Context#getExternalFilesDir(String) Context.getExternalFilesDir(null).
<external-cache-path name="name" path="path" />
Context.getExternalCacheDir()
在使用external-files-path和external-cache-path需要注意,這兩個是在support v4 24以上FileProvider才添加的
private static final String TAG_EXTERNAL_FILES = "external-files-path";
private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
3.為一個文件生成Content URI
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
生成URI:content://com.mydomain.fileprovider/my_images/default_image.jpg4.為URI臨時授權,兩種方法
1)Context.grantUriPermission(package, Uri, mode_flags);
// mode_flags可以設置為 FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION
權限失效:用戶取消權限[revokeUriPermission() ]或者手機重啟
2)Intent intent = new Intent();
intent.setData(Uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
setResult(RESULT_OK,intent);
權限失效:返回處理結果Activity所在的stack結束
5.發(fā)送這個URI給其他APP橱脸,兩種方法 1)startActivityResult() 2)借助ClipData處理
附錄2:最近路徑容易搞混础米,也打印記錄下
Context.getFilesDir()=/data/data/com.harry.shopping/files
Context.getCacheDir()=/data/data/com.harry.shopping/cache
Environment.getExternalStorageDirectory()=/storage/emulated/0
getExternalFilesDir(Environment.DIRECTORY_PICTURES)=/storage/emulated/0/Android/data/com.harry.shopping/files/Pictures
Context.getExternalFilesDir(null)=/storage/emulated/0/Android/data/com.harry.shopping/files
Context.getExternalCacheDir()=/storage/emulated/0/Android/data/com.harry.shopping/cache
總結
這類問題比較簡單分苇,查看報錯Log,參考官方文檔屁桑,借助FileProvider可以方便的處理這類安全問題医寿。
參考文獻:
https://developer.android.com/training/camera/photobasics.html
https://developer.android.com/reference/android/support/v4/content/FileProvider.html