getExternalStoragePublicDirectory在Android Q中已弃用

Per*_*abs 14 android

由于getExternalStoragePublicDirectory已在中弃用Android Q,建议使用其他方式。那么我们如何指定我们要将相机应用程序生成的照片存储到DCIM文件夹或文件夹中的自定义子文件夹中DCIM呢?

文档指出,接下来的3个选项是新的首选替代品:

  1. Context#getExternalFilesDir(String)
  2. 媒体商店
  3. 意图#ACTION_OPEN_DOCUMENT

选项1不存在,因为这意味着如果卸载该应用程序,照片将被删除。

选项3也不是一个选择,因为它将要求用户通过SAF文件浏览器选择位置。

我们只剩下选项2,即MediaStore;但是没有文档说明如何使用它代替Android Q中的getExternalStoragePublicDirectory。

Gau*_*all 34

@CommonsWare 的回答很棒。但是对于那些想要在 Java 中使用它的人,你需要试试这个:

ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
Run Code Online (Sandbox Code Playgroud)

根据@SamChen 的建议,文本文件的代码应如下所示:

Uri uri = resolver.insert(MediaStore.Files.getContentUri("external"), contentValues);
Run Code Online (Sandbox Code Playgroud)

因为我们不希望 txt 文件留在 Images 文件夹中。

所以,我有的地方mimeType,你输入你想要的 mime 类型。例如,如果你想要txt(@Panache) 你应该mimeType用这个字符串替换:"text/plain"。这里是一个 mime 类型列表:https : //www.freeformatter.com/mime-types-list.html

另外,在我有变量的地方name,你用你的情况下的文件名替换它。

  • 对于文本文件,请记住`Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), value);` (2认同)

Com*_*are 19

根据该文档,使用DCIM/...RELATIVE_PATH,哪里...是什么您的自定义子目录会。因此,您将得到如下所示的结果:

      val resolver = context.contentResolver
      val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "CuteKitten001")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/PerracoLabs")
      }

      val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

      resolver.openOutputStream(uri).use {
        // TODO something with the stream
      }
Run Code Online (Sandbox Code Playgroud)

请注意,由于RELATIVE_PATH是API级别29的新增功能,因此您需要在较新的设备上使用此方法,而getExternalStoragePublicDirectory()在较旧的设备上使用。

  • 感谢您的回答。附带说明一下,我只是在文档中看到还有一个新字段“ IS_PENDING”,我猜测也许在创建ContentValues时也应该包括在内,以便在编写文件时安全,然后进行更新文件完全保存后立即保存。 (3认同)
  • @androiddeveloper:“所以现在只有SAF可以访问文件了?” -AFAIK,是的。“然后MediaStore获得仅用于媒体文件的全局文件(我不知道什么是“媒体”,除了一些常见的图像,视频和音频文件之外)?” -AFAIK,是的。 (3认同)
  • 添加您自己的文章:https://commonsware.com/blog/2019/04/22/death-external-storage-more-story.html。 (2认同)
  • 我想知道是否真的没有办法实施一次。这行得通,但是我之前已经想通了。我不一定要继续使用```getExternalStoragePublicDirectory()`以及新方法。现在,我拥有多个if / else来确定Android版本是否舍弃了代码库,从而可以使用新方法和旧方法进行操作。 (2认同)
  • 这种新方法不允许您访问以前创建的文件,不是吗?例如,如果我制作了一个相机应用程序,将文件放入您提到的文件夹中,然后删除并重新安装我的应用程序,我将无法再访问它们,不是吗?我认为存储权限被其他权限取代,这些权限受到更多限制,不(也许只能处理媒体文件)?另外,是否有允许创建的所有可能文件类型的列表?这些只是媒体文件,还是任何文件(如 PDF、ZIP、APK...)? (2认同)
  • @androiddeveloper:“如果我制作了一个相机应用程序,将文件放在您提到的文件夹中,然后删除并重新安装我的应用程序,我将无法访问它们,不是吗?” -正确。从Android的角度来看,您以前安装的应用程序中的文件与其他任意应用程序中的文件相同。“我认为存储权限已被其他权限所取代,这些权限受到更多限制,不是(也许只能处理媒体文件)?” -他们摆脱了Beta 3中的那些。 (2认同)
  • @androiddeveloper:“是否存在允许创建的所有可能文件类型的列表?” -不是我知道的。“这些只是媒体文件还是任何文件(例如PDF,ZIP,APK等)?” -我认为就使用`MediaStore`而言,您仅限于媒体文件。 (2认同)
  • @androiddeveloper:我确定您已经阅读了https://developer.android.com/preview/privacy/scoped-storage和https://developer.android.com/preview/features#create-files-external-存储。在这些主题的Android Q更改标准文档中,我什么都没有知道。 (2认同)
  • @androiddeveloper:“如果没有存储许可,媒体播放器应用(视频,音频和图像)到底会从Q上的OS请求什么?” -他们要求“ READ_EXTERNAL_STORAGE”。在https://developer.android.com/preview/privacy/scoped-storage中进行了介绍。 (2认同)
  • 我看到了@CommonsWare。似乎我已经跳过了这一部分。我记得,所以存储许可大大减少了。它仅允许访问媒体文件,即使在默认情况下,它也无法获取有关它们的所有信息(元数据)。而且它甚至不允许使用文件路径访问这些文件,因为如果允许,那意味着我可以只使用InputStream来获取媒体文件中的所有内容,包括受保护的元数据...对吗? (2认同)
  • @androiddeveloper:该描述符合我的理解。 (2认同)
  • “ACCESS_MEDIA_LOCATION”显然是他们将我们从文件系统中抽象出来的灵魂原因。如果没有此权限,您将无法看到拍摄照片的位置时间戳(通常为 .jpg 格式,因此您可以从文件本身而不是媒体存储中提取它)。当您没有权限时,您可以访问的 .jpg 会抛出 mediastore 提供的输入流,您将从流中删除这些信息。仅使用存储权限,以便媒体存储允许您访问非您自己创建的媒体。 (2认同)