详细使用说明请参考hongyang的blog
http://blog.csdn.net/lmj623565791/article/details/72859156
如果我们使用Android 7.0或者以上的原生系统,再次运行一下,你会发现应用直接停止运行,抛出了android.os.FileUriExposedException
:
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)
此文主要讲一下,关于使用fileprovider的采坑过程。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.xxx.xxxx.fileprovider" 注意这个参数要和代码中启动的authorities保持一致
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
provider>
为什么要声明呢?因为FileProvider是ContentProvider子类哇~~
注意一点,他需要设置一个meta-data,里面指向一个xml文件。
注意:
exported
:要求必须为false,为true则会报安全异常。grantUriPermissions:true
,表示授予 URI 临时访问权限。authorities
组件标识,按照江湖规矩,都以包名开头,避免和其它应用发生冲突。android:resource="@xml/file_paths"
指的是当前组件引用 res/xml/file_paths.xml
这个文件。
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
paths>
为什么hongyang不写成这样? 真是让人迷惑
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="path" />
<files-path name="files" path="path" />
<cache-path name="cache" path="path" />
<external-path name="external" path="" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
paths>
在paths节点内部支持以下几个子节点,分别为:
代表设备的根目录new File("/")
;
代表context.getFilesDir()
代表context.getCacheDir()
代表Environment.getExternalStorageDirectory()
代表context.getExternalFilesDirs()
代表getExternalCacheDirs()
每个节点都支持两个属性:
的时候代码没问题,所有手机运行都没问题
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="internal_files" path="." />
<external-files-path name="external_files" path="." />
paths>
路径内部apkFile = context.getExternalFilesDirs()+"/"xx.apk; 所有版本手机安装成功
我这里要将服务器中的apk下载到应用user/data的私有空间和下载到外部存储两种,我的配置文件如下
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="internal_files" path="download" />
<external-files-path name="external_files" path="download" />
paths>
/**
* 安装 apk 文件
*
* @param apkFile
*/
public void installApk(File apkFile) {
Intent installApkIntent = new Intent();
installApkIntent.setAction(Intent.ACTION_VIEW);
installApkIntent.addCategory(Intent.CATEGORY_DEFAULT);
installApkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
installApkIntent.setDataAndType(FileProvider.getUriForFile(getApplicationContext(),"com.xxx.xxxx.fileprovider", apkFile), "application/vnd.android.package-archive");
installApkIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
installApkIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}
if (getPackageManager().queryIntentActivities(installApkIntent, 0).size() > 0) {
startActivity(installApkIntent);
}
}
apkFile = context.getFilesDir()+"/download"+xx.apk; 下载到内部存储的时候发现了新问题,原来在android6.0以下安装好使的代码,不好使了。原因是找不到安装文件。
apkFile = context.getExternalFilesDirs()+"/download/"+xx.apk; 下载到外部存储的时候代码没问题,所有手机运行都没问题。
于是修改配置文件和下载路径
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="internal_files" path="." />
<external-files-path name="external_files" path="." />
paths>
路径内部apkFile = context.getExternalFilesDirs()+"/"xx.apk; 所有版本手机安装成功
外部apkFile = context.getFilesDir()+"/"+xx.apk; 所有版本手机安装成功
生成 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 后,便可以通过 startActivity() 或者 setResult() 方法启动其他应用并传递授权过的 Content URI 数据。当然,也有其他方式提供服务。
如果你需要一次性传递多个 URI 对象,可以使用 intent 对象提供的 setClipData() 方法,并且 setFlags() 方法设置的权限适用于所有 Content URIs。
下面的写法是粘贴的老外一篇文章里的,应该是斜杠和点的作用是一样的。
name="cache" path="/" /> name=”files” path=”/” />
总结:就是在installApkIntent.setDataAndType(Uri.fromFile(apkFile),
"application/vnd.android.package-archive"
);
startActivity(installApkIntent);
android 6.0以下的手机,安装应用user/data下面的apk时,只支持目录为context.getFilesDir()的层级,再深一层,系统都找不到这个下载后的文件安装包了。可能是6.0以下的默认权限问题。