Android 11 文件分区存储在图片读写的适配
说明
当 APP 目标版本是 Android 10(API 29)及以后时,由于 Android 引入了分区存储,APP 不能直接通过路径访问文件,访问外部存储空间中的媒体文件除了需要READ_EXTERNAL_STORAGE
或 WRITE_EXTERNAL_STORAGE
权限之外,需要通过其他 APP 分享的Uri
读写文件,同理要给其余 APP 分享文件也许要通过FileProvider
生成Uri
并赋予对应的权限。
本文以从相册中获取图片、请求系统裁剪并返回图片为例展示对应的适配方法。
实际操作
1.从相册中获取图片
从相册中获取到的图片Uri
一般如:content://raw//storage/emulated/0/DCIM/Camera/IMG_20210531_183008.HEIC
app 内部要读取其内容的话,可以通过context.getContentResolver().openInputStream(imageUri)
或者下面这种方式读取,操作该图片。
Cursor cursor = context.getContentResolver().query(uri, filePathColumn, null, null, null);//从系统表中查询指定 Uri 对应的照片
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
if (columnIndex >= 0) {
picturePath = cursor.getString(columnIndex);
}
2.将外部文件保存到本地并获取 Uri
由于上述方式获取到的Uri
只对本 APP 赋予了权限,要是希望将此图片分享给第三方 APP 进一步加工处理,则可能出现第三方 APP 没有读写权限而导致操作失败的情况,为了避免这种情况,可以将获取到的图片缓存到 APP 私有目录,并且重新生成Uri
并赋予将要处理该图的第三方 APP 对应权限。
将外部文件缓存本地的步骤参考第一步操作即可自行完成,主要讲解一下如何将对外分享的Uri
赋予读写权限。
下面这个方法在不同系统分别采用不同方式获取文件对应的Uri
。
public static Uri getUriForFile(Context context, File file) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return FileProvider.getUriForFile(context, "com.your.app.packagename.fileprovider", file);
} else {
return Uri.fromFile(file);
}
}
其中com.your.app.packagename.fileprovider
是FileProvider
的authorities
。
要使用FileProvider
可以参考定义 FileProvider操作,一般只需要修改authorities
即可,同时如果是开发 Android 库,为了避免与主工程已有的FileProvider
冲突,可以继承FileProvider
类并修改下文中name
字段。
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
...
</application>
</manifest>
同时,为了定义此FileProvider
可以使用的文件目录范围,可以在res/xml
文件夹中新建file_paths.xml
并做如下配置,也可参考官方文档或者Android N 7.0 FileProvider 兼容适配 原理解析:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path
name="camera_photos"
path="" />
<external-files-path // 对应Context#getExternalFilesDir(String)获取的路径,一般为存储卡中Android/data/com.your.app.packagename/file下面的目录
name="external_files"
path="." />
</paths>
3.对外分享有权限的 Uri
对于上述步骤获取到的图片 Uri 赋予权限有两种方式:
第一种,通过Intent
传递出去的imgUri
,可以使用以下方式:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(imgUri, "image/*");
但是这种只适用于主动分享出去的文件,在调用第三方 APP 裁剪的场景中,一般还需要一个outPutUri
用于保存裁剪之后的图片,对于这种场景,可以查询可能会调用的 APP 并赋予其访问权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
List<ResolveInfo> resInfoList = context.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_ALL);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
contextWrap.getActivity().grantUriPermission(packageName, outPutUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
这样不管是分享出去的原图, 还是裁剪之后保存的图片都给第三方 APP 赋予了权限, 保证其可以正常访问。
参考文献
https://developer.android.google.cn/reference/androidx/core/content/FileProvider