Android 11 适配之分区存储

修改点: 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值