还是上次适配 Android O 的 Adaptive Icons 时候留下的一个大坑。。。。。。
测试的同事确认没什么问题可以发布新版本了,开开心心上线后发现我们自己的手机一更新就会崩溃,而测试的同事却没有问题,赶紧撤下上线了的版本,经检查发现原来同事的手机还运行着 Android 4.4,而我们的手机已经是 Android 8.0。
在我们适配 Android 8.0 的时候将 compileSdkVersion 和 targetSdkVersion 的版本都升级到了 26,由此带来的问题就是没有注意到 Android 7.0 以上的新特性,具体 Android N 的全新特性可以查看Android 7.0 行为变更。
在 Android 7.0 以前,在下载新版本 apk 后,使用 Intent 安装 apk 文件的代码大致如下:
1 2 3 4 5
| val intent = Intent(Intent.ACTION_VIEW) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val file = File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk") val uri = Uri.fromFile(file) intent.setDataAndType(uri, "application/vnd.android.package-archive")
|
但在 Android N 上,为了更好的控制权限和注重安全隐私,Google 进行了全新的规定:
1 2
| 对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。 要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。如需了解有关权限和共享文件的详细信息,请参阅共享文件。
|
当然本身这项适配工作并没有难度,这里也只是记录一下适配的方法。
首先是第一步,在清单文件中进行申明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <manifest> ... <application> ... <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.xxxx.fileprovider" android:exported="false" android:grantUriPermissions="true"> ... </provider> ... </application> </manifest>
|
接下来我们要创建resource xml file:
1 2 3
| <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="my_docs" path="docs/"/> </paths>
|
这里需要解释一下,在paths节点内部支持以下几个子节点:
代表设备的根目录new File(“/“);
代表context.getFilesDir()
代表context.getCacheDir()
代表Environment.getExternalStorageDirectory()
代表context.getExternalFilesDirs()
代表getExternalCacheDirs()
我们可以根据位置的不同选择对应的节点。
当然创建了文件之后我们需要在清单文件中注明。
1 2 3 4 5 6 7 8 9
| <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.xxxx.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider>
|
这个时候基本的配置已经完成了,我们还需要在代码中做一个版本的判断。回到我们 install apk 的方法。
Uri 不能单纯的通过 Uri.fromFile() 函数来完成,
1 2 3 4 5 6
| val uri = if (SDK_INT >= N) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) FileProvider.getUriForFile(this, "com.xxxx.fileprovider", file) } else { Uri.fromFile(file) }
|
完成之后提交合并打包,然后再一次尝试应用内升级,一步成功~
不得不说,以后需要多阅读 Android 的官方文档,尤其是涉及新版本适配的时候,很多坑完全是可以避免的。