Android Q中文件沙盒模式读写文件 08-16

嘛~好久没写了

当前Android Q中启用了沙盒存储模式,限制了APP向SDcard中读写文件,当前谷歌尚未将该限制强制执行,可以通过设置属性启用旧存储特性。[原文]

沙盒模式下,每个APP在访问sdcard时会进入过滤视图,只能访问私有路径(Context.getExternalFilesDir())和公共存储空间(多媒体,MediaStore)。

官方文档给对应的解决方案,但是在我看来并不详尽,目前可以看到有以下三个解决方案:

文件位置 所需权限 访问方法 (*) 卸载应用时是否移除文件? 是否需要权限
特定于应用的目录 getExternalFilesDir()
媒体集合 (照片、视频、音频) READ_EXTERNAL_STORAGE (仅当访问其他应用的文件时) MediaStore
下载内容 (文档和 电子书籍) 存储访问框架 (加载系统的文件选择器)

除了第一种写在“/sdcard/Android/data/packageName/file”路径中可以正常使用InputStream&OutputStream读写文件,另外两种方法都无法直接对文件IO操作。
所以后续有向SDcard中写文件的需求优先向APP私有路径中写入。

但是,需要解决APP之间共享文件的场景,比如手机唯一标识配置文件(UUID啥的),这种需求场景需要对MediaStore和SAF存储访问框架做调研。

一、存储访问框架

访问存储框架是一个基于intent发起的文件检索UI,一般只能在View存在时调用系统的文件检索框架,在startActivityForResult() 的回调中收到文件对象。

/**
   * Fires an intent to spin up the "file chooser" UI and select an image.
   * 视图检索文件
   */
  public void performFileSearch() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("*/*");
    startActivityForResult(intent, READ_REQUEST_CODE);
  }

但咱是写SDK的,不能在FileManager单例或者静态方法中读文件的话,对咱毫无意义

二、MediaStore

MediaStore是外部存储空间的公共媒体集合,存放的都是多媒体文件,在API >= 29后加入了download集合

一般Android中通过手机数据库查询Uri来对MediaStore中的文件做查询,然后读取文件,下面以MediaStore.Files查询所有文件为例:

  private void testFiles() {
    ContentResolver contentResolver= this.getApplicationContext().getContentResolver();
    String [] photoColumns=new String[]{
        MediaStore.Files.FileColumns._ID,
        MediaStore.Files.FileColumns.DATA,
        MediaStore.Files.FileColumns.TITLE,
        MediaStore.Files.FileColumns.MIME_TYPE,
        MediaStore.Files.FileColumns.SIZE
    };
    Cursor cursor;
    cursor=contentResolver.query
        (MediaStore.Files.getContentUri("external"), photoColumns,
            null,
            null,
            null);
    while (cursor.moveToNext()) {
      String _id=
          cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
      String filePath=
          cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
      String title=
          cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE));
      String mime_type=
          cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE));
      Log.d(TAG, "_id = " + _id);
      Log.d(TAG, "title = " + title);
      Log.d(TAG, "filePath = " + filePath);
      Log.d(TAG, "mime_type = " + mime_type);
    }
    Log.d(TAG, "Finish Cursor Search");
  }

通过上述代码可以在所有文件中找到指定文件,和绝对路径。同理,换成Image、Video、Audio、Downloads都一样,只是在数据库查询时把Uri替换掉。

PS: 但是查询有个致命伤,在开启沙盒模式时,只能在数据库中查询到Image、Video、Audio三个分类视图中的文件,那么我SDK向SDcard中写入的配置文件就不能在沙盒模式下通过数据库查询到。

只能获取到mime_type == {“image/”, “video/”, “audio/*”}这三类文件

这可怎么办呀

上边代码中通过ContentResolver.query查询到文件详情,但如果需要修改文件内容,则使用ContentResolver.openFileDescriptor()获取单个文件描述符。配置文件拿不到,先不管这个文件读写了。

小结

目前只能先使用官方支持的规避方案:通过设置android:requestLegacyExternalStorage="false"属性来关闭APP的沙盒机制。
我还会回来的

推荐阅读更多精彩内容