Android 短视频和图片无读写权限TargetApi>=29解决方案

一、背景

        目前很多公司在适配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进行操作。

这里主要有:

  1.         内容从外存转到内存;
  2.         uri scheme的协议转换;
  3.         contentResolver在target 29后的查询,只支持_name和_size
  4.         短视频的时长获取

猜你喜欢

转载自blog.csdn.net/qq36246172/article/details/128785727