近期项目上(N版本mtk平台)遇到一个问题,问题描述如下:
预置条件:插入sd卡并设置sd卡为默认存储
操作步骤:通过蓝牙接收辅助机发过来的文件,在接收通知中打开
问题现象:弹出蓝牙共享停止运行
分析这个问题前,先来讨论下FileProvider
从Android 7.0开始,一个应用提供自身文件给其它应用使用时,如果给出一个file://格式的URI的话,应用会抛出FileUriExposedException。这是由于谷歌认为目标app可能不具有文件权限,会造成潜在的问题。对此,谷歌官方推荐使用FileProvider
FileProvider的使用步骤
Step1:
在manifest中声明一个provider
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
android:resource="@xml/file_paths" />
其中authorities可以自定义,不过一定要和java代码中FileProvider.getUriForFile里的的保持一致。
Step2:
编写file_paths.xml, file_paths.xml
中编写该Provider对外提供文件的目录,文件放置在res/xml/下。
格式如下:
内部的element从Android官方文档上看支持如下几种path类型:
(1)
该方式提供在应用的内部存储区的文件/子目录的文件。它对应Context.getFilesDir返回的路径:eg:”/data/data/com.jph.simple/files”。
(2)
该方式提供在应用的内部存储区的缓存子目录的文件。它对应getCacheDir返回的路径:eg:“/data/data/com.jph.simple/cache”;
(3)
该方式提供在外部存储区域根目录下的文件。它对应Environment.getExternalStorageDirectory返回的路径:eg:”/storage/emulated/0”;
(4)
该方式提供在应用的外部存储区根目录的下的文件。它对应Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)返回的路径。eg:”/storage/emulated/0/Android/data/com.jph.simple/files”。
(5)
该方式提供在应用的外部缓存区根目录的文件。它对应Context.getExternalCacheDir()返回的路径。
eg:”/storage/emulated/0/Android/data/com.jph.simple/cache”。
这些类型在Android手机内部存储区文件共享是可以行的通的,这里需要注意的是,以上几种path类型对于外置SD卡是不行的,如果你想通过FileProvider.getUriForFile()获取一个外置SD卡的Uri则会报出IllegalArgumentException异常,后面会进一步说明。
Step3:
在Java代码当中使用以分享一个图片为例:
File file = ...; //要分享的图片文件
Uri uri = FileProvider.getUriForFile(context, "com.mydomain.fileprovider", file); //第二个参数是manifest中定义的authorities
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_TITLE, title);
intent.putExtra(Intent.EXTRA_TEXT, text);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //这一步很重要。给目标应用一个临时的授权。
startActivity(intent); //或者其它最终处理方式
现在重新回到我们这个问题,依次按照上面的三步检查下
1.首先看下packages/apps/Bluetooth/AndroidManifest.xml中
android:grantUriPermissions="true"
android:exported="false">
android:resource="@xml/file_paths" />
2.紧接着看packages/apps/Bluetooth/res/xml/file_paths.xml
3.最后再看下代码的用法
try {
path = FileProvider.getUriForFile(context,
"com.google.android.bluetooth.fileprovider", f);
} catch (IllegalArgumentException e) {
Log.e(TAG, "FileProvider can't find path. " + e.toString());
// M : use normal trick instead.
path = Uri.parse(fileName);
}
activityIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activityIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
看上去三个步骤都有,不过这里注意到FileProvider.getUriForFile用try/catch起来了,通过查看日志发现
走到FileProvider.getUriForFile报了IllegalArgumentException异常,也就是说此时path的获取实际是通过Uri.parse(fileName);
catch中看注释 path = Uri.parse(fileName);这句话应该是芯片商mtk加的,至于mtk为什么这么加我们不用去管。
前面说过在N版本上如果给出一个file://格式的uri,则会上报FileUriExposedException,这里的Uri.parse(fileName)便会导致这个问题
再来看这里为什么会上报IllegalArgumentException,注意一下我们这个问题是默认存储是sd卡,这个又回到上面Step2中的提到的
file_paths.xml中的几种path类型,这几种类型都是谷歌官方文档列出的,其实还有一个root-path没有列出,当需要获取外置sd卡的uri时,使用
谷歌官方文档列出的几种都会报出IllegalArgumentException,所以这里需要在file_paths.xml中添加一条
这个停止运行的问题也就解决了。
参考文章:
http://www.jianshu.com/p/68a4e8132fcd
http://blog.csdn.net/honjane/article/details/52057132