一、背景
目前很多公司在适配API29,也就是targetSdkVersion=29的权限适配。不仅是权限的适配,还有政策的要求。
目前就有很多大公司已收到工信部要求,不给读写权限:
android.permission.WRITE_EXTERNAL_STORAGE和
android.permission.READ_EXTERNAL_STORAGE
这种情况下,你再想通过ContentResolver去查询系统表中是无法满足给所有的Image和Video查出来。所以,我们只能通过系统方法来处理,而且在外存上的东西,我们是无法读取的,杀手锏就是把外存的东西搬到内存里,在data下面开辟属于我们自己包下的存储路径。
二、权限适配
在没有读写权限下的方案,选中的图片存储到系统里面,为什么要选择copy这种方式?因为在没有读写权限时,文件在外存,也就是SD卡的时候,是无法进行一系列的操作,如果存储到内存中data目录下,是可以进行正常的读写操作,也不需要权限。
1、图片
1.1选择图片:
单选:
Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT");
intent.setType("image/*");
intent.putExtra("android.intent.extra.MIME_TYPES", new String[]{"image/*"});
多选:
Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT");
intent.setType("image/*");
intent.putExtra("android.intent.extra.MIME_TYPES", new String[]{"image/*"});
intent.putExtra("android.intent.extra.ALLOW_MULTIPLE", true);
1.2 选择后保存到data目录下:
单选和多选的返回处理不同:
多选:
ClipData clipData = data.getClipData();
if (clipData != null) {
Uri child = clipData.getItemAt(0).getUri();
}
多选的数据存储子啊ClipData中,从里面可以遍历出来 单选:
Uri uri = data.getData();
保存到data目录下: 通过Uri获取到bitmap: bitmap = Media.getBitmap(context, uri);
public String saveFileIntoDataCache(Context context, String fileName, Bitmap bitmap) {
FileOutputStream fos = null;
String path = null;
try {
File folder = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (folder.exists() || folder.mkdir()) {
File file = new File(folder, fileName);
fos = new FileOutputStream(file);
bitmap.compress(CompressFormat.JPEG, 100, fos);
fos.flush();
path = file.getAbsolutePath();
}
} catch (Exception var16) {
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException var15) {
}
}
return path;
}
2、短视频
选择:已mp4为例,如果是*,将会把所有的video都会显示出来
视频也是一样,区分单选和多选
Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT", Media.EXTERNAL_CONTENT_URI);
intent.setType("video/mp4");
多选新增一个:
intent.putExtra("android.intent.extra.ALLOW_MULTIPLE", true);
返回的结果也是一样,可以参考上面图片的处理,多选在ClipData,单选就是一个data
,其他也是一样,根据uri,将文件copy进data下方
3、uri的协议scheme
uri的有一个关键的type叫协议,scheme,不同的版本协议也不一样。有人在文件选择回来发现协议是file://,并非content://。这个时候,再想通过ContentResolver去操作是返回null的。
遇到这种情况,我们需要转换协议
//路径转Uri
fun getContentPathToUri(packageName:String,path: String, context: Context): Uri {
val AUTHORITY=" ${packageName}.fileprovider"
var inputUri: Uri? = null
val filePath = File(path)
if (Build.VERSION.SDK_INT >= 24) {
// 通过FileProvider创建一个content类型的Uri
inputUri = FileProvider.getUriForFile(context, AUTHORITY, filePath)
} else {
val inputUri = Uri.fromFile(filePath)
}
return inputUri!!
}
三、文件信息获取
使用文件的时候,我们常常需要获取文件的大小和名称之类。因为这些文件都存储在表中,所以获取是通过查询接口。但是,有些公司的应用targetapi已提到了29或者更高。在查询结果后,获取和以往有所不同。
val course = contentResolver.query(uri, null, null, null, null)
if (course != null && course.moveToFirst()) {
course.getString(course.getColumnIndex(OpenableColumns.DISPLAY_NAME))
course.getString(course.getColumnIndex(OpenableColumns.SIZE))
}
以往都是通过media下面获取,由于29后,已不在提供详细的。
只能获取名称和大小。这个时候有人会发现,如果我需要短视频的时长怎么办?
视频获取时长:MediaMetadataRetriever
fun getVideoTimeLength(uri: Uri, context: Context): Int {
var metadataRetriever = MediaMetadataRetriever()
metadataRetriever.setDataSource(context, uri)
val longtime =
metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
if (TextUtils.isEmpty(longtime)) {
return 0
}
return longtime!!.toInt()
}
如果需要读取uri中的内容,如下
ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri,"rw");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
四、总结
在没有读写权限以及targetapi调整到29时,又因为内存中读取不需要权限,所以策略调整是将外存的内容复制到内存中,再通过内存的uri进行操作。
这里主要有:
- 内容从外存转到内存;
- uri scheme的协议转换;
- contentResolver在target 29后的查询,只支持_name和_size
- 短视频的时长获取