问题的引出是在一次版本更新的时候,我把编译版本上升到了API26,于是对于安卓7.0的手机在更新后安装apk的过程中出现闪退,查看错误提示FileUriExposedException。查阅资料得知原来是在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照,或裁切照片等。那么,怎么解决这个问题呢?
谷歌给出的方案是使用FileProvider
step1.在AndroidManifest.xml文件的application节点下面注册provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.jph.takephoto.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
step2.指定共享目录
具体做法是:在res目录下新建名为xml的目录,然后新建一个资源文件。如上面的文件名就叫“file_paths” 可以随便去。但是要和注册文件里的一致
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--外置SD卡-->
<root-path name="root" path="" />
<!--Context.getFilesDir()-->
<files-path name="files" path="" />
<!--Context.getCacheDir()-->
<cache-path name="cache" path="" />
<!--Environment.getExternalStorageDirectory()-->
<external-path name="external" path="" />
<!--Context.getExternalFilesDir(null)-->
<external-files-path name="external_file_path" path="" />
<!--Context.getExternalCacheDir()--> <!--直到support-v4:25.0.0才支持-->
<external-cache-path name="external_cache_path" path="" />
</paths>
step3.使用FileProvider
File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri imageUri = FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider", file);//通过FileProvider创建一个content类型的Uri
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
startActivityForResult(intent,1006);
正常情况下,这么做就可以了,实际开发中难免会使用第三方,很多第三方也对这个做了兼容,并且他们往往会这么写
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
这样会造成多个provider的冲突,可能你看见log日志里面提示你使用 tools:replace这个属性,然而当你这么干了之后,灾难性的的事情发生了,第三方的provider出问题了。那么怎么做呢,我的解决方案要求不需要去改动第三方,对于远程依赖的第三方这种做法无疑是最完美的。
写一个provider继承FileProvider
public class MyProvider extends FileProvider {
}
改写主项目中注册的provider
<provider
android:name="com.androidquanjiakan.base.MyProvider"
android:authorities="${applicationId}.android24.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths1" />
</provider>
我把name,authorities以及resource改变了,这样解决了与第三方的冲突问题
最后,封装一个获取uri的工具类兼容7.0的特性
public class FileProviderUtil {
public static Uri getUriForFile(Context context, File file) {
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= 24) {
fileUri = getUriForFile24(context, file);
} else {
fileUri = Uri.fromFile(file);
}
return fileUri;
}
public static Uri getUriForFile24(Context context, File file) {
Uri fileUri = android.support.v4.content.FileProvider.getUriForFile(context,
context.getPackageName() + ".android24.fileprovider",
file);
return fileUri;
}
public static void setIntentDataAndType(Context context,
Intent intent,
String type,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= 24) {
intent.setDataAndType(getUriForFile(context, file), type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setDataAndType(Uri.fromFile(file), type);
}
}
public static void setIntentData(Context context,
Intent intent,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= 24) {
intent.setData(getUriForFile(context, file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setData(Uri.fromFile(file));
}
}
public static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) {
int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (writeAble) {
flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
}
intent.addFlags(flag);
List<ResolveInfo> resInfoList = context.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, flag);
}
}
}
网友评论