修改点:?
1.权限修改
修改权限申请
(1)Read的权限是保留的,如果想要访问公共资源都是要声明和动态申请读取权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
动态验证和申请权限的方式和之前一致
申请之后系统弹框的文案较之前有了变化,会凸显出 access photos and media?
(2)写入权限
写入权限在11中被彻底废弃了,想要写入需要通过mediaStore和SAF框架,不需要权限就可以通过这两种API写入文件到指定目录。
Android10可以使用leagcy的flag保持之前的行为。再声明write权限可以申请maxSdkVerision
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
动态申请:
if (Build.VERSION.SDK_INT > 28){
isGrantedPermissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE)
}else {
isGrantedPermissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_PHONE_STATE)
}
2.sdcard文件访问路径修改
Android11 应用特有目录
一部分是内部存储,一部分是外部存储,
内部存储访问
Context.getFilesDir()
/data/user/0/com.kuaima.storage/files
Context.getCacheDir()
/data/user/0/com.kuaima.storage/cache
通过外部存储访问:
Context.getExternalCacheDir()
/storage/emulated/0/Android/data/com.kuaima.storage/cache
Context.getExternalMediaDirs()
/storage/emulated/0/Android/media/com.kuaima.storage
//此目录可以被MediaStore扫描到,在Android11接口被废弃,但仍可使用
(1)SAF框架
该框架会弹出一个系统级的选择器,用户需要手动操作才能完整走完读写流程,由于用户在操作的时候相当于已经授权了,所以该框架调用不需要权限
读取:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
startActivityForResult(intent, 100)
@RequiresApi(Build.VERSION_CODES.KITKAT)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (data == null || resultCode != Activity.RESULT_OK) return
if (requestCode == 100) {
val uri = data.data
println("image uri is $uri")
}
}
写入:
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf");
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse(" <http://10.110.101.219:8088/somatosensory.pdf>"));
2.MediaStore
MediaStore有固定的几个Type,获得对应的URI如下
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
MediaStore.Files.getContentUri("external")
读取:
读取同上需要先动态申请读取权限
如读取图片:
String[] columns_2 = new String[]{MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
//指定文件目录名查询,只对地址模糊匹配会造成数据重复
String selection = MediaStore.Images.Media.DATA + " like ? and " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " = ?";
String[] selectionArgs = {new File(ifb.path).getParentFile().getAbsolutePath() + "%",ifb.fileName};
Cursor corsor = null;
try {
corsor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns_2, selection, selectionArgs,
null);
// android 10 打开分区存储时:这个地址加载图片失败
String path = corsor.getString(corsor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
/storage/emulated/0/objModel/Madinat_Zayed_Hospital/Untitled-45_749.jpg
int id = corsor.getInt(corsor.getColumnIndex(MediaStore.Images.ImageColumns._ID));
17984
// 兼容 分区存储打开前后
Uri contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
);
content://media/external/images/media/17984
写入:通过MediaStore写入文件, 运行在Android11上不需要权限也可以写入成功
例如1:写入bitmap 图片
private suspend fun performWriteImage(bitmap: Bitmap) {
withContext(Dispatchers.IO) {
val contentValues = ContentValues()
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "test.jpg")
contentValues.put(MediaStore.Images.Media.DESCRIPTION, "test.jpg")
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
val uri = context.contentResolver.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
try {
val outStream = context.contentResolver.openOutputStream(uri!!)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream)
outStream?.close()
} catch (securityException: SecurityException) {
}
}
}
例子2:拍照
// 直接启动照相机,照相机照片将会存在默认的文件中
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues()
// 需要指定文件信息时,非必须
values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image")
values.put(MediaStore.Images.Media.DISPLAY_NAME, System.currentTimeMillis().toString() + ".jpg")
values.put(MediaStore.Images.Media.TITLE, System.currentTimeMillis().toString() + ".jpg")
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/albumCameraImg")
imgUri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
} else {
//一加手机uri异常,修改拍照目录固定至外部目录下
imgUri = FileProvider.getUriForFile(context, context.packageName + ".provider",
File(
StorageUtils.getExternalStoragePublicDirectory("")
+ File.separator + System.currentTimeMillis().toString()))
// imgUri = FileProvider.getUriForFile(context, context.packageName + ".provider", File(System.currentTimeMillis().toString() + ".jpg"));
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
context.startActivityForResult(intent, OPEN_CAMERA_CODE)
删除操作:如果是在公共目录里删除自己写的文件也不需要权限,如果要删除其它应用写入的文件则每次删除都会弹框提示用户
private suspend fun performDeleteImage(image: MediaStoreImage) {
withContext(Dispatchers.IO) {
try {
context.contentResolver.delete(
image.contentUri,
"${MediaStore.Images.Media._ID} = ?",
arrayOf(image.id.toString())
)
} catch (securityException: SecurityException) {
}
}
}
其他应用 需要权限会进到securityException里,申请完权限后再进行相同的删除操作就可以了
参考文章:
掘金
Android 11存储适配 - 简书
https://juejin.cn/post/6860370635664261128#heading-3
|