Android 10 分区存储

本文介绍了Android 10开始引入的分区存储,旨在解决应用数据混乱和隐私问题。通过MediaStore和SAF,开发者可以安全地访问公共目录。MediaStore用于媒体文件操作,无需额外权限,而SAF适用于非媒体文件和需要用户授权的情况。同时,文章详细展示了如何使用这两个API进行文件的创建、删除、查询和权限管理。
摘要由CSDN通过智能技术生成

背景

以前,Android 开发者习惯在根目录建一个自己应用的文件夹,用于存放应用的数据。这样会导致用户卸载后,应用数据不会随之删除。导致手机文件特别混乱,长期占用空间,而且容易泄露用户隐私。

其实 Android 早就提供了 getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir() 等 API 供开发者使用,但是开发者为了方便,没有去用。

为了解决这个问题,从 Android 10 开始,Google 添加了一个新特性 Scoped Storage,我们称之为分区存储,也可以称为沙盒。

在 Android 10 上,仍然可以通过以下两种手段避开分区存储:

  1. targetSdkVersion 设成 29 以下
  2. 在 manifest 中设置 android:requestLegacyExternalStorage=“true”

在 Android 11 上,requestLegacyExternalStorage 会失效,没有效果。但是又增加了 preserveLegacyExternalStorage 属性,对于覆盖安装的应用还能继续用,但是新应用不能用。

至于 targetSdkVersion,上传到 Google Play 的应用,Google 要求必须设成 30 及以上。

分区存储目录

  1. 沙盒目录
    通过 getExternalFilesDir() 等获取到的目录,随着 App 卸载会被删除。
    不过可以在 manifest 中设置 android:hasFragileUserData=“true” 让用户选择是否删除。

  2. 公共目录
    DCIM、Photos、Images、Videos、Audio、Downloads 等目录, App 卸载后会保留。

访问公共目录

重点说下公共目录,沙盒目录就不详细介绍了,沙盒目录可以通过系统提供的接口直接获取,可以直接通过路径读写,也不需要定义任何读写权限,很简单。

访问公共目录需要通过 MediaStore 或者 Storage Access Framework(以下简称 SAF)。媒体文件(图片,音频,视频)能通过 MediaStore 和 SAF 两种方式访问,非媒体文件只能通过 SAF 访问。

MediaStore

关于 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 读写权限,MediaStore 访问应用自身存放到公共目录下的文件不需要申请权限(但是如果应用卸载后重装,之前保存的文件将不属于本应用创建的文件),而如果要访问其他应用保存到公共目录下的文件则需要申请权限

MediaStore 通过 Uri 操作文件。
各个目录的 Uri 如下:

类型 Uri Uri 常量 默认路径
Image content://media/external/images/media MediaStore.Images.Media.EXTERNAL_CONTENT_URI Pictures
Video content://media/external/video/media MediaStore.Video.Media.EXTERNAL_CONTENT_URI Movies
Audio content://media/external/audio/media MediaStore.Audio.Media.EXTERNAL_CONTENT_URI Music
Download content://media/external/downloads MediaStore.Downloads.EXTERNAL_CONTENT_URI Download
File content://media/external/ MediaStore.Files.getContentUri(“external”) Documents
写文件
// 从 Assets 读取 Bitmap
Bitmap bitmap = null;
try {
   
    bitmap = BitmapFactory.decodeStream(getAssets().open("test.jpg"));
} catch (IOException e) {
   
    e.printStackTrace();
}

if (bitmap == null) return;

// 获取保存文件的 Uri
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
Uri insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

// 保存图片到 Pictures 目录下
if (insertUri != null) {
   
    OutputStream os = null;
    try {
   
        os = contentResolver.openOutputStream(insertUri);
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
    } catch (FileNotFoundException e) {
   
        e.printStackTrace();
    } finally {
   
        try {
   
            if (os != null) {
   
                os.close();
            }
        } catch (IOException e) {
   
            e.printStackTrace();
        }
    }
}

上面的例子直接把图片保存到 Pictures 根目录,如果要在 Pictures 下创建子目录,需要用到 RELATIVE_PATH(Android 版本 >= 10)。

修改上面的例子,把子目录添加进 ContentValues:

ContentValues values = new ContentValues();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
   
    // 指定子目录,否则保存到对应媒体类型文件夹根目录
    values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES +"/test");
}

还可以向 ContentValues 中添加其他信息,如:文件名,MIME 等
继续修改上面的例子:

ContentValues values = new ContentValues();
// 获取保存文件的 Uri
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
// 指定保存的文件名,如果不设置,则系统会取当前的时间戳作为文件名
values.put(MediaStore.Images.Media.DISPLAY_NAME, "test_" + System.currentTimeMillis() + ".png");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
   
    // 指定子目录,否则保存到对应媒体类型文件夹根目录
    values.put
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值