7.0系统上面出现了很多的变化;这里主要是记录FileProvider的变更影响
主要体现在两个地方:安装应用 和 相机拍照
1,应用安装
在7.0系统以前通常是通过action这样子进行安装的
Intent install = new Intent(Intent.ACTION_VIEW); String pathString = intent.getStringExtra("downloadFile"); Uri uri = Uri.fromFile(new File(pathString));; install.setDataAndType(uri, "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); M_MainActivity.this.startActivity(install);
当运行在7.0及以上系统中是;会报错
Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/20170601-030254.png exposed beyond app through ClipData.Item.getUri() at android.os.StrictMode.onFileUriExposed(StrictMode.java:1932) at android.net.Uri.checkFileUriExposed(Uri.java:2348)
官方的解释是:
对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
官方的解决方案:
要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。如需了解有关权限和共享文件的详细信息,请参阅共享文件。
使用FileProvider 对 安装文件进行授权
<!--7.0系统 运行Uri.fromFile()修改--> <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider>
在res -> xml -> provider_paths.xml <?xml version="1.0" encoding="utf-8"?> <resource xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="hm_file" path=".../files"/> </resource>
Intent install = new Intent(Intent.ACTION_VIEW);
String pathString = intent.getStringExtra("downloadFile");
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
uri = FileProvider.getUriForFile(M_MainActivity.this
, getPackageName()+".fileprovider", new File(pathString));
}else
{
uri = Uri.fromFile(new File(pathString));
}
install.setDataAndType(uri, "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
M_MainActivity.this.startActivity(install);
2.拍照;
同样,7.0以下的拍照通常是这样的
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { String filename = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.CHINA) .format(new Date()) + ".png"; File file = new File(Environment.getExternalStorageDirectory(), filename); mCurrentPhotoPath = file.getAbsolutePath(); Uri fileUri = FileProvider.getUriForFile(this, "com.zhy.android7.fileprovider", file); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PHOTO); }
适配7.0以及以上系统
Uri fileUri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { fileUri = FileProvider.getUriForFile(this, "com.zhy.android7.fileprovider", file); } else { fileUri = Uri.fromFile(file); }
备注:
因为我们在7.0以上系统中exported必须是false;但是在以下系统中必须是true;否则在低版本上运行会报错
Caused by: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{952c094 4768:com.google.android.packageinstaller/u0a18} (pid=4768, uid=10018) that is not exported from ui
上面都是通过红色部分来进行授权的;还有一种就是通过context提供的两个方法来授权
grantUriPermission(String toPackage, Uri uri, int modeFlags) revokeUriPermission(Uri uri, int modeFlags);
可以看到grantUriPermission需要传递一个包名,就是你给哪个应用授权,但是很多时候,比如分享,我们并不知道最终用户会选择哪个app,所以我们可以这样:
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); }