随着Android 版本升级 Google在保护用户隐私和安全方面增加了力度 像Android 6.0 细分了普通权限和高危权限,涉及到高危权限则需要及时申请用户必须感知。Android7.0 增加了 "StrictMode API" 政策即私有目录限制访问。本篇讲解一下如何在Android 7.0上实现应用内安装APK。
系统权限更改
为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问 (0700)。此设置可防止私有文件的元数据泄漏。
1.私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 或MODE_WORLD_WRITEABLE 而进行的此类尝试将触发SecurityException。
2.传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。
3.DownloadManager 不再按文件名分享私人存储的文件。旧版应用在访问 COLUMN_LOCAL_FILENAME 时可能出现无法访问的路径。面向 Android 7.0 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发SecurityException。通过使用DownloadManager.Request.setDestinationInExternalFilesDir() 或 DownloadManager.Request.setDestinationInExternalPublicDir() 将下载位置设置为公共位置的旧版应用仍可以访问 COLUMN_LOCAL_FILENAME 中的路径,但是我们强烈反对使用这种方法。对于由 DownloadManager 公开的文件,首选的访问方式是使用ContentResolver.openFileDescriptor()。
应用间共享文件
对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。
FileProvider使用
1.在AndroidManifest.xml清单文件中注册provider,因为provider也是Android四大组件之一,可以简单把它理解为向外提供数据的组件,这种组件在实际开发中用的频率并不高,四大组件都可以在清单文件中进行配置。
//${package_name}为包名
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${package_name}.fileProvider" android:exported="false" android:grantUriPermissions="true"> <!-- 元数据 --> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
2.指定共享的目录上面配置文件中 android:resource="@xml/file_paths" 指的是当前组件引用 res/xml/file_paths.xml 这个文件。我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,
<?xml version="1.0" encoding="utf-8"?> <resources> <paths> <!-- files-path: 该方式提供在应用的内部存储区的文件/子目录的文件。 它对应Context.getFilesDir返回的路径:eg:”/data/data/com.jph.simple/files”。 cache-path: 该方式提供在应用的内部存储区的缓存子目录的文件。 它对应getCacheDir返回的路径:eg:“/data/data/com.jph.simple/cache”; external-path: 该方式提供在外部存储区域根目录下的文件。 它对应Environment.getExternalStorageDirectory返回的路径: external-cache-path: 该方式提供在应用的外部存储区根目录的下的文件。 它对应Context#getExternalFilesDir(String) Context.getExternalFilesDir(null) 返回的路径。eg:”/storage/emulated/0/Android/data/com.jph.simple/files” --> <external-path name="download" path="" /> </paths> </resources>
上述代码中path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。
如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
3.使用FileProvider上述准备工作做完之后,现在我们就可以使用FileProvider了。我们需要将上述安装APK代码修改为如下
File apkFile = new File(Environment.getExternalStorageDirectory(), "doc.apk"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //判断是否是AndroidN以及更高的版本 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); } else { Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileProvider", apkFile); intent.setDataAndType(uri, "application/vnd.android.package-archive"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } context.startActivity(intent);
当然了要是在6.0以上系统别忘记需要提前申请存储卡操作权限[WRITE_EXTERNAL_STORAGE]
android 8.0安装apk需要请求未知来源权限:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
上述代码中主要有两处改变:
- 将之前Uri改成了有FileProvider创建一个content类型的Uri。
- 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。
上述代码通过FileProvider的Uri getUriForFile (Context context, String authority, File file)静态方法来获取Uri该方法中authority参数就是清单文件中注册provider时填写的 authority android:authorities="com.xxxx.xxxx.xxxx"按照上面步骤修改就可以兼容Android7.0了。