美文网首页
Android N FileUriExposedExcepti

Android N FileUriExposedExcepti

作者: 最忆是深秋 | 来源:发表于2018-05-22 18:03 被阅读70次

一. 背景

   Andriod N及以上版本中,一个应用提供自身文件给其它应用使用时,如果是file://格式的URI的话,会由StrictMode 触发FileUriExposedException 异常, 通常是长这样的:

05-21 17:12:07.438: E/AndroidRuntime(29373): FATAL EXCEPTION: main
05-21 17:12:07.438: E/AndroidRuntime(29373): Process: test.fileprovider, PID: 29373
05-21 17:12:07.438: E/AndroidRuntime(29373): android.os.FileUriExposedException: file:///storage/emulated/0/youbing.apk exposed beyond app through Intent.getData()
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1958)
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.net.Uri.checkFileUriExposed(Uri.java:2348)
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.content.Intent.prepareToLeaveProcess(Intent.java:9766)
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.content.Intent.prepareToLeaveProcess(Intent.java:9720)
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1614)
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.app.Activity.startActivityForResult(Activity.java:4508)
05-21 17:12:07.438: E/AndroidRuntime(29373):    at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFrag

官方是建议我们使用Fileprovider来解决这种问题,当然我们也可以在应用内通过添加一个不设防的VmPolicy来规避。

二. FileProvider的方式

1. AndroidManifest中申明FileProvider
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.test.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

android:authorities="com.test.fileprovider": 这个名称自己定义
android:exported="false":表明这个provider不用对外开放
android:grantUriPermissions="true": 这个一定要为true, 否则无法获得临时权限

2. res/xml中定义需要对外暴露的文件夹路径

<paths>里边的元素必须是一下的一个或者多个, 对要分享出去的文件,一定要声明 正确的路径,否则会报如下错误:

05-22 10:43:52.491: E/AndroidRuntime(19432): FATAL EXCEPTION: main
05-22 10:43:52.491: E/AndroidRuntime(19432): Process: test.fileprovider, PID: 19432
05-22 10:43:52.491: E/AndroidRuntime(19432): java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/youbing.apk
05-22 10:43:52.491: E/AndroidRuntime(19432):    at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:712)
05-22 10:43:52.491: E/AndroidRuntime(19432):    at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:401)
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_path" path="."/>
    <external-files-path name="external_files_path" path="."/>
    <external-cache-path name="external_cache_path" path="."/>
    <files-path name="files_path" path="."/>
    <cache-path name="cache_path" path="."/>
    <root-path name="root_path" path="."/>
</paths>

name:名称标志字符串,不可以同名!
path:文件夹“相对路径”,完整路径取决于当前的标签类型。

标签 对应目录
<files-path name="name" path="path" /> /data/data/pkgname/files/path/
<external-files-path name="name" path="path" /> /storage/emulated/0/Android/data/pkgname/files/path/
<external-cache-path name="name" path="path" /> /storage/emulated/0/Android/data/pkgname/cache/path/
<files-path name="name" path="path" /> /data/data/pkgname/files/path/
<cache-path name="name" path="path" /> /data/data/pkgname/cache/path/
<root-path name="name" path="path" /> /path/
3. getUriForFile生成content://类型的Uri
String str = "/youbing.apk";
String fileName = Environment.getExternalStorageDirectory().getAbsolutePath() + str;
File file = new File(fileName);
Uri uri = FileProvider.getUriForFile(MainActivity.this, "com.test.fileprovider", file);

通过FileProvider 提供的接口 getUriForFile(context, string , file)获取处理后的Uri, 参数二 就是在AndroidManifest.xml中声明的 android:authorities="com.test.fileprovider"的值

4. 给Uri授予临时权限,并使用Intent传递Uri
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(intent);

我这里的demo是从apk中尝试调起系统安装器安装 一个apk的场景,调用代码如下:

String str = "/youbing.apk";
String fileName = Environment.getExternalStorageDirectory().getAbsolutePath() + str;
File file = new File(fileName);
Uri uri = FileProvider.getUriForFile(MainActivity.this, "com.test.fileprovider", file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(intent);

三. VmPolicy 方式

这个很简单,通常就是在 Application的 oncreate方法中添加两句代码:

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

四. 遇到过的与FileProvider相关的问题

  1. 在写这个demo中遇到的,因为场景是调起系统安装器,按照上面两种方式处理后还是无法调起安装器,原因是我的 targetSdkVersion 是 26, 对应Android 8.0。因此需要在 AndroidManifest中声明权限,否则无法调起PackageInstaller.
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
  1. 王者荣耀自升级,安装时提示 安装包解析失败
    具体场景是 :进入王者荣耀后,下载完升级包,会自动拉起PackageInstaller进行安装,然后就弹出 安装包解析失败对话框,无法完成升级
    分析原因: 进入王者荣耀后,是横屏的,Android O上,下载完升级包拉起PackageInstaller的过程会依次进入 InstallStart - InstallStaging - PackageInstallerActivity 这几个activity,其中 InstallStart 和PackageInstallerActivity是指定强制竖屏,并且不随屏幕方向变化重走 activity 生命周期的
android:screenOrientation="portrait"    如果只指定强制竖屏,从横屏跳转过来时还是会走两次生命周期
android:configChanges="orientation|keyboardHidden|screenSize"

而InstallStaging重走了生命周期,onDestroy掉重新onCreate. 导致FileProvider授予的临时权限丢失,访问不到安装包文件URI,最终解析失败。
framework中临时权限的回收调用栈,从调用栈可以看到Uri临时访问权限确实随着目标activity的 destroy一起被移除掉了:

    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.ActivityManagerService.removeUriPermissionIfNeededLocked(ActivityManagerService.java:10961)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.UriPermissionOwner.removeUriPermissionLocked(UriPermissionOwner.java:95)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.UriPermissionOwner.removeUriPermissionsLocked(UriPermissionOwner.java:69)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.UriPermissionOwner.removeUriPermissionsLocked(UriPermissionOwner.java:64)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.ActivityRecord.removeUriPermissionsLocked(ActivityRecord.java:1537)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.ActivityStack.removeActivityFromHistoryLocked(ActivityStack.java:4119)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.ActivityStack.activityDestroyedLocked(ActivityStack.java:4344)
    05-22 15:52:03.848: E/john(17382):  at com.android.server.am.ActivityManagerService.activityDestroyed(ActivityManagerService.java:9125)

解决办法: 当然就是在源码PackageInstaller的AndroidManifest.xml中给InstallStaging activity标签内添加

android:screenOrientation="portrait"   
android:configChanges="orientation|keyboardHidden|screenSize"

防止由activity重建导致的权限丢失

相关文章

网友评论

      本文标题:Android N FileUriExposedExcepti

      本文链接:https://www.haomeiwen.com/subject/xeshjftx.html