📌  相关文章
📜  面向 Android 10+ 时,WRITE_EXTERNAL_STORAGE 不再提供写访问权限 - Java (1)

📅  最后修改于: 2023-12-03 14:58:44.371000             🧑  作者: Mango

面向 Android 10+ 时,WRITE_EXTERNAL_STORAGE 不再提供写访问权限 - Java

自从 Android 10 开始,WRITE_EXTERNAL_STORAGE 不再提供直接访问外部存储的方式。应用程序需要适配新的存储访问架构来保持兼容性。

对外部存储的更改

在 Android 10 及更高版本中,应用程序只能使用以下方式来访问外部存储:

  • 使用 MediaStore API:应用程序可以使用 MediaStore API 查看、修改或删除用户媒体文件,如图像、音频或视频。使用 MediaStore API 还需要请求 READ_EXTERNAL_STORAGE 权限。

  • 使用 Storage Access Framework(SAF)API:应用程序可以使用 SAF API 来访问用户选择的根目录。To let users select a whole directory subtree, use ACTION_OPEN_DOCUMENT_TREE。使用 SAF API 还需要请求 READ_EXTERNAL_STORAGE 权限。

  • 如果应用程序需要访问应用程序特定目录下的文件,在 Android 10 上,应该创建一个 app-specific 目录,并在其下创建子目录和文件。这些目录和文件可以使用 Context.getExternalFilesDir() 和 Context.getExternalCacheDir() 方法。

如何适配 Android 10 存储访问架构

对于最常见的媒体文件管理,使用 MediaStore API。

以下是在 Android 10 及更高版本中使用迁移 MediaStore API 的示例代码:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Environment.isExternalStorageLegacy()) {
  // Only use legacy storage API for Android 10 devices or above
  // when external storage is already mounted
  final String DCIMCameraDir = "/DCIM/Camera/";
  final String DIRECTORY_PICTURES = "/Pictures/";
  final String IMAGE_MIME_TYPE = "image/jpeg";
  final String VIDEO_MIME_TYPE = "video/mp4";

  // Get the Image and Video MediaStore collections
  final Uri images = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
  final Uri videos = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);

  // Find all JPEGs and save them to the camera folder
  final Cursor imageCursor = getContentResolver().query(
      images,
      new String[] { MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA },
      MediaStore.Images.Media.MIME_TYPE + "=? OR " + MediaStore.Images.Media.MIME_TYPE + "=?",
      new String[] { IMAGE_MIME_TYPE, "image/png"},
      null);
  while (imageCursor.moveToNext()) {
    final long id = imageCursor.getLong(0);
    final String path = imageCursor.getString(1);
    final File file = new File(path);
    final String newImagePath = DCIMCameraDir + file.getName();
    final ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DATA, newImagePath);
    values.put(MediaStore.Images.Media.IS_PENDING, 1);
    getContentResolver().update(images, values, MediaStore.Images.Media._ID + "=?", new String[]{Long.toString(id)});
    file.renameTo(new File(newImagePath));
    values.clear();
    values.put(MediaStore.Images.Media.IS_PENDING, 0);
    getContentResolver().update(images, values, MediaStore.Images.Media._ID + "=?", new String[]{Long.toString(id)});
  }

  // Find all videos and save them to the camera folder
  final Cursor videoCursor = getContentResolver().query(
      videos,
      new String[] { MediaStore.Video.Media._ID, MediaStore.Video.Media.DATA },
      null,
      null,
      null);
  while (videoCursor.moveToNext()) {
    final long id = videoCursor.getLong(0);
    final String path = videoCursor.getString(1);
    final File file = new File(path);
    final String newVideoPath = DCIMCameraDir + file.getName();
    final ContentValues values = new ContentValues();
    values.put(MediaStore.Video.Media.DATA, newVideoPath);
    values.put(MediaStore.Video.Media.IS_PENDING, 1);
    getContentResolver().update(videos, values, MediaStore.Video.Media._ID + "=?", new String[]{Long.toString(id)});
    file.renameTo(new File(newVideoPath));
    values.clear();
    values.put(MediaStore.Video.Media.IS_PENDING, 0);
    getContentResolver().update(videos, values, MediaStore.Video.Media._ID + "=?", new String[]{Long.toString(id)});
  }
  imageCursor.close();
  videoCursor.close();
} else {
  // For devices running Android 9 and lower, or when external storage is not mounted
  // keep using getExternalStoragePublicDirectory()
  // See code samples for managing files on older devices and volumes
}

对于其他类型的文件操作,使用 SAF API。

以下是使用 Storage Access Framework(SAF)API 的示例代码:

// Use the ACTION_OPEN_DOCUMENT tree intent to request access to the attached storage device.
private static final int EXTERNAL_STORAGE_REQUEST_CODE = 100;
private void requestStoragePermissionForSAF() {
  Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
  startActivityForResult(intent, EXTERNAL_STORAGE_REQUEST_CODE);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
  if (requestCode == EXTERNAL_STORAGE_REQUEST_CODE && resultCode == RESULT_OK) {
    if (resultData != null) {
      // Use the provided uri to access the attached storage device.
      Uri treeUri = resultData.getData();

      getContentResolver().takePersistableUriPermission(
          treeUri,
          Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

      // the application can access files from device
    }
  }
}

以上就是适配 Android 10 存储访问架构的示例代码和操作说明。

总结

在 Android 10 及更高版本中,应用程序需要适配新的存储访问架构来保持兼容性。使用 MEDIASTORE API 和 SAF API 可以满足访问不同类型文件的需求。注意:在 Android 10 中,请求 WRITE_EXTERNAL_STORAGE 权限已不再提供,应用程序不应再使用此权限来访问外部存储。