众所周知在android7.0,修改了对私有存储的限制,导致在获取资源的时候,不能通过Uri.fromFile来获取uri了我们需要适配7.0+的机型需要这样写:
1:代码适配
if (Build.VERSION.SDK_INT > 23) {//
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context, SysInfo.packageName + ".fileProvider", outputFile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
}
2:创建provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
<external-path name="beta_external_path" path="Download/"/>
<!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
<external-path name="beta_external_files_path" path="Android/data/"/>
</paths>
其中 provider_path属性详解
2.cache-path
代表与getCacheDir()相同的文件路径
3.external-path
4.external-files-path
5.external-cache-path
代表与Context.getExternalCacheDir()相同的文件路径
6:配置AndroidManifest.xml
android:authorities在FileProvider中使用
7:使用FileProvider
*** 返回URI:content://com.mydomain.fileprovider/my_images/default_image.jpg.
8.自定义FileProvider
3:我们项目中可能会用到其他一些第三方sdk有用到拍照功能的话,他也为了适配android7.0也添加了这个节点,此时有些人可能就不知道如何下手了,其实很简单我们只要重写一个类 继承自FileProvider,然后就按上述方法在添加一个节点就可以了:
<provider
android:name="com.blueZhang.MyFileProvider"
android:authorities="${applicationId}.provider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/cust_file_paths" />
</provider>
如果你不想自定义FileProvider,那么还有一种方法,那就是把第三方sdk中的路径配置copy到provider_paths.xml即可。
如下所示:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
<external-path name="beta_external_path" path="Download/"/>
<!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
<external-path name="beta_external_files_path" path="Android/data/"/>
<external-path name="external_storage_root" path="."/>
<files-path name="files" path="."/>
</paths>
注意⚠️:在使用provider时 配置路径 path="."代表所有路径
生成 Content URI
在 Android 7.0 出现之前,我们通常使用 Uri.fromFile()
方法生成一个 File URI。这里,我们需要使用 FileProvider
类提供的公有静态方法 getUriForFile
生成 Content URI。比如:
Uri contentUri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".fileProvider", myFile);
需要传递三个参数。第二个参数便是 Manifest 文件中注册 FileProvider 时设置的 authorities 属性值,第三个参数为要共享的文件,并且这个文件一定位于第二步我们在 path 文件中添加的子目录里面。
举个例子:
String filePath = Environment.getExternalStorageDirectory() + "/images/"+System.currentTimeMillis()+".jpg";
File outputFile = new File(filePath);
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdir();
}
Uri contentUri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".fileProvider", outputFile);
content://com.yifeng.samples.myprovider/my_images/1493715330339.jpg
其中,构成 URI 的 host 部分为 <provider>
元素的 authorities 属性值(applicationId + customname),path 片段 my_images 为 res/xml 文件中指定的子目录别名(真实目录名为:images)。
第四步,授予 Content URI 访问权限
生成 Content URI 对象后,需要对其授权访问权限。授权方式有两种:
第一种方式,使用 Context 提供的 grantUriPermission(package, Uri, mode_flags)
方法向其他应用授权访问 URI 对象。三个参数分别表示授权访问 URI 对象的其他应用包名,授权访问的 Uri 对象,和授权类型。其中,授权类型为 Intent 类提供的读写类型常量:
FLAG_GRANT_READ_URI_PERMISSION
FLAG_GRANT_WRITE_URI_PERMISSION
或者二者同时授权。这种形式的授权方式,权限有效期截止至发生设备重启或者手动调用 revokeUriPermission()
方法撤销授权时。
第二种方式,配合 Intent 使用。通过 setData()
方法向 intent 对象添加 Content URI。然后使用 setFlags()
或者 addFlags()
方法设置读写权限,可选常量值同上。这种形式的授权方式,权限有效期截止至其它应用所处的堆栈销毁,并且一旦授权给某一个组件后,该应用的其它组件拥有相同的访问权限。
第五步,提供 Content URI 给其它应用
拥有授予权限的 Content URI 后,便可以通过 startActivity()
或者 setResult()
方法启动其他应用并传递授权过的 Content URI 数据。当然,也有其他方式提供服务。
如果你需要一次性传递多个 URI 对象,可以使用 intent 对象提供的 setClipData()
方法,并且 setFlags()
方法设置的权限适用于所有 Content URIs。
前面介绍的内容都是理论部分,在 开发者官方 FileProvider 部分 都有所介绍。接下来我们看看,实际开发一款应用的过程中,会经常遇见哪些 FileProvider 的使用场景。
自动安装文件
版本更新完成时打开新版本 apk 文件实现自动安装的功能,应该是最常见的使用场景,也是每个应用必备功能之一。常见操作为,通知栏显示下载新版本完毕,用户点击或者监听下载过程自动打开新版本 apk 文件。适配 Android 7.0 版本之前,我们代码可能是这样:
File apkFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_sample.apk");
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
startActivity(installIntent)
现在为了适配 7.0 及以上版本的系统,必须使用 Content URI 代替 File URI。
在 res/xml 目录下新建一个 file_provider_paths.xml 文件(文件名自由定义),并添加子目录路径信息:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_download" path="Download"/>
</paths>
然后在 Manifest 文件中注册 FileProvider 对象,并链接上面的 path 路径文件:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.yifeng.samples.myprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
</provider>
修改 java 代码,根据 File 对象生成 Content URI 对象,并授权访问:
File apkFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_sample.apk");
Uri apkUri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID+".fileProvider", apkFile);
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(installIntent);
好了 有不明白的 及时联系