第一篇讲一下android N使用中对于Uri形式读取文件的不同之处。
场景
在使用相机拍照生成头像时,之前的逻辑是,先用intent唤起照相机,并传入一个图片输出的地址,拍摄完毕后,再用intent唤起系统的照片crop,同样传入一个output地址,对相片进行剪辑,最后上传处理完成的照片。
原因分析
在Android N之前,对于Uri形式文件的访问一般采用Uri.fromFile(File file)。但在7.0中,官方对于文件访问的安全性做了提升,禁止向您应用外的app公开 file://URI。也就是系统照片的crop应用无法访问你的照片文件,否则会出现FileUriExposedException异常。详情参阅在应用间共享文件
解决方法
首先在AndroidManifest.xml中声明一个provider
...
...
android:name="android.support.v4.content.FileProvider"
android:authorities="com.your.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
...
然后在/res/xml目录下新建一个file_paths.xml,声明需要访问的目录。<?xml version="1.0" encoding="utf-8"?>
name="external_files"
path="." />
其中的元素代表了不同的根目录CellPathfiles-pathContext.getFilesDir().
cache-pathgetCacheDir().
external-pathEnvironment.getExternalStorageDirectory().
external-files-pathContext.getExternalFilesDir(String)
external-cache-pathContext.getExternalCacheDir().
然后使用fileprovider读取文件
原先Uri = Uri.fromFile(new File(strFilePath))
改为现在Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".fileProvider", new File(strFilePath));
上面解释过了,如果只是在app内读取uri,那么到现在已经完成了,但是如果要调用外部app来访问这个file(例如调用系统的crop对相片进行裁剪),则还需要添加授权操作。具体的操作就是在调用crop前,授予权限。List resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
这里提供了一个较为简便的方法,如果你不知道你所调用的app的包名,可以遍历所有的package,都授予读写权限。
总结
安全性上,android 7.0做了很大的提升,需要开发者多注意。是坑,亦是革新。
备注
在Andorid 7.0系统下,当app对相机和外置储存卡进行使用操作的时候,都需要先请求用户权限,允许以后才可以调用。这个将在下一篇当中讲解。