Android 10(Api 29)新特性适配 – 分区存储

官方文档:https://developer.android.google.cn/preview/privacy/scoped-storage

问题描述

从Android 10开始应用将不可直接访问外部存储(/sdcard)文件,否则抛异常。

在AndroidQ上运行:

  • targetSdkVersion<Q,没影响;
  • targetSdkVersion>=Q,默认启用过滤视图,应用以外的文件需要通过存储访问框架SAFStorageAccessFramework)读写。

解决方法

停用过滤视图,使用旧版存储模式

<manifest ... >
    <!-- This attribute is "false" by default on apps targeting Android Q. -->
    <application android:requestLegacyExternalStorage="true" ... >
       ...
    </application>
</manifest>

将文件存储到过滤视图中,官方推荐。

// /Android/data/com.example.androidq/files/Documents
File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
  • 优点:不用申请读写权限
  • 缺点:随应用卸载而删除

使用存储访问框架(SAF),由用户指定要读写的文件。

此功能Android 4.4(API: 19)就有,官方文档:https://developer.android.google.cn/guide/topics/providers/document-provider

获取用户指定的某个目录的读写权限

从Android 5.0(Api 21)开始就有,官方文档:https://developer.android.google.cn/about/versions/android-5.0#Storage

申请目录的访问权限

会打开系统的文件目录,由用户自己选择允许访问的目录,不用申请WRITE/READ_EXTERNAL_STORAGE权限。

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | 
        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);                
startActivityForResult(intent, REQ_CODE);

执行上述代码后会出现类似如下图界面,点击‘允许访问“DuoKan”’按钮

Uri读写文件询问框

允许之后通过onActivityResult()intent.getData()得到该目录的Uri,通过Uri可获取子目录和文件。这种方式的缺点是应用重装后权限失效,即使保存这个Uri也没用。

Uri dirUri = intent.getData();
// 持久化;应用重装后权限失效,即使知道这个uri也没用
SPUtil.setValue(this, SP_DOC_KEY, dirUri.toString());
//重要:少这行代码手机重启后会失去权限
getContentResolver().takePersistableUriPermission(dirUri, 
        Intent.FLAG_GRANT_READ_URI_PERMISSION);

通过Uri读写文件

(1)创建文件

// 在mUri目录(‘DuoKan’目录)下创建'test.txt'文件
private void createFile() {
    DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri);
    DocumentFile file = documentFile.createFile("text/plain", "test.txt");
    if (file != null && file.exists()) {
        LogUtil.log(file.getName() + " created");
    }
}

主要用到DocumentFile类,和File类的方法类似,有isFileisDirectoryexistslistFiles等方法

(2)删除文件

//删除"test.txt"
private void deleteFile() {
    DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri);
    // listFiles(),列出所有的子文件和文件夹
    for (DocumentFile file : documentFile.listFiles()) {
        if (file.isFile() && "test.txt".equals(file.getName())) {
            boolean delete = file.delete();
            LogUtil.log("deleteFile: " + delete);
            break;
        }
    }
}

(3)写入数据

private void writeFile(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "w");
        //这种方法会覆盖原来文件内容
        OutputStreamWriter output = 
                new OutputStreamWriter(new FileOutputStream(pfd.getFileDescriptor()));
        // 不能传uri.toString(),否则FileNotFoundException
        // OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(uri.toString(), true));
        output.write("这是一段文件写入测试\n");
        output.close();
        LogUtil.log("写入成功。");
    } catch (IOException e) {
        LogUtil.log(e);
    }
}

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/18/android-10-api-29-new-feature-adaptation-partitioned-storage/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Android 10(Api 29)新特性适配 – 分区存储
官方文档:https://developer.android.google.cn/preview/privacy/scoped-storage 问题描述 从Android 10开始应用将不可直接访问外部存储(/sdcard)文件,否……
<<上一篇
下一篇>>
文章目录
关闭
目 录