Android 11 适配分区存储,完美保存共享媒体文件,兼容低版本

如何共享视频

我们通用方式是,先把视频通过直接路径的方式保存到SD卡,然后通过MediaColumns.DATA把文件路径注册到MediaStore中。这样相册就能看到这个视频了。
从Android10开始引入了分区存储的概念,在Android的分区存储模型中,保存需要共享的视频时,推荐(其实就是强制)使用先在MediaStore中注册获取一个Uri,会默认生成一个路径的,把Uri用输出流的方式打开,然后写入需要共享的视频。我们把这种方式叫Uri共享视频的方式
通过这种方式才能共享视频,因为在Android10的分区存储模型中,是禁止通过直接路径访问SD卡的,否则报FileNotFoundException错,即便是共享媒体目录也不行,比如/storage/emulated/0/Movies或者/storage/emulated/0/Pictures。虽然到了Android11的分区存储模型时,可以通过直接路径File的方式访问了,(注意:也只是能访问共享媒体目录,SD卡其它目录还是禁止通过直接路径访问)但是,获取路径的Api Environment.getExternalStoragePublicDirectory或Environment.getExternalStorageDirectory()在Android10 也就是29被标记为弃用了,这个意思就是说在29及以上系统,禁止使用或慎用这些api。也就是说在Android11分区存储模型上,也是不让用直接路径访问共享媒体的。
也就是说,在Android10及以上的系统只能通过Uri的方式共享媒体文件。

我们发现,在29以下的系统也是可以用先在MediaStore注册的方式保存需要共享的媒体的,这样的话,所有版本系统都用一套方案就行了。

    java.io.FileNotFoundException: /storage/emulated/0/XXX/SharePic/1603277403193.jpg: open failed: ENOENT (No such file or directory)
        at libcore.io.IoBridge.open(IoBridge.java:496)
        at java.io.FileOutputStream.(FileOutputStream.java:235)
        at com.xxx.QrCodeActivity.askSDCardSaveImgPermission(QrCodeActivity.java:242)
        at com.xxx.QrCodeActivityPermissionsDispatcher.askSDCardSaveImgPermissionWithPermissionCheck(QrCodeActivityPermissionsDispatcher.java:27)
        at com.xxx.QrCodeActivity.saveImageToLocal(QrCodeActivity.java:229)
        at com.xxx.QrCodeActivity.saveQrCodeBitmap(QrCodeActivity.java:202)
        at com.xxx.QrCodeActivity.onClick(QrCodeActivity.java:152)
        at android.view.View.performClick(View.java:7258)
        at android.view.View.performClickInternal(View.java:7220)
        at android.view.View.access$3800(View.java:821)
        at android.view.View$PerformClick.run(View.java:27712)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:7840)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:985)

为了进行使用真实路径,Android 10 开始把MediaColumns.DATA都deprecated了。
看下面几篇文章,就知道怎么保存共享视频的

问题来了

    /**
     * 保存共享视频,必须使用先在MediaStore创建表示视频保存信息的Uri,然后通过Uri写入视频数据的方式。
     * 在"分区存储"模型中,这是强制的,因为禁止直接通过路径File的方式保存视频
     * 从Android 10开始默认是分区存储模型
     * @param context
     * @return
     */
    public static Uri getSaveToGalleryVideoUri(Context context, String videoName) {
     
        Log.d("lzy", "getSaveToGalleryVideoUri videoName " + videoName);
        //TODO 很奇怪,保存视频居然是3gp格式的,怎么设置都改不成mp4的,还需研究
        ContentValues values = new ContentValues();
        values.put(MediaStore.Video.Media.DISPLAY_NAME,  videoName + ".mp4");
        values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
     
            values.put(MediaStore.Video.Media.RELATIVE_PATH, "sohuVideo");
        }

        return context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
    }

在29系统以下,采用Uri的方式保存共享视频,MediaStore生成的默认目录是/storage/emulated/0/video/1603364148293.3gp,是video目录,而不是Movies目录。而且,DISPLAY_NAME指定文件名也是无效的,实际的文件名是时间戳。而且,MIME_TYPE也是无效的,最后保存的是3gp的,怎么都折腾不成mp4的。保存目录和文件名都不重要,重要的是在29系统以下共享的视频都是3gp格式的,这个就接受不了了。

在29系统以下,采用Uri的方式保存共享图片,也有问题,MediaStore生成的默认目录是/storage/emulated/0/Pictures/1603364148162.jpg,这个目录是对的,在Pictures目录,默认是按照jpg保存的,不过DISPLAY_NAME和MIME_TYPE也是无效的。

低系统对Uri保存共享媒体这种方式支持的不够完善!!!

解决问题

一文带你了解Android 11分区存储适配

参考

访问共享存储空间中的媒体文件
处理外部存储中的媒体文件
Scoped Storage Stories: Storing via MediaStore
添加项目
Insert Video to gallery [Android Q]
AndroidQ(10)分区存储完美适配之下载图片(文件)本地
Android MediaStore insertVideo

你可能感兴趣的:(Android,分区存储,Android,10)