从 Android 10 开始,Android 引入了一种新的存储权限模型,其中应用需要明确请求运行时权限以访问外部存储。在这种情况下,许多应用程序可能需要请求 WRITE_EXTERNAL_STORAGE 权限才能将文件下载到公共目录(例如 "Download" 目录)。这是为了提高用户的隐私和数据安全。
但是,有一些应用可能使用特定的权限或 API 或者具有特定的使用案例,可以绕过运行时权限请求。这通常是因为这些应用可能是系统应用、文件管理器、云存储应用、或者拥有其他特权。这些应用可能不受相同的权限限制。
在一些情况下,应用可能会使用不需要特定存储权限的公共存储 API(例如 MediaStore 或 DownloadManager)来保存文件到公共目录。这些 API 可能会更加灵活,但通常仍受到安全限制,以确保访问的合法性。
如果您注意到其他应用可以在不申请存储权限的情况下将文件下载到公共目录,那可能是因为它们在特权环境中运行,或者它们使用了特殊的 API,或者它们使用了其他方式来绕过权限限制。不过,通常情况下,普通的应用仍然需要明确请求存储权限,以确保合法的文件操作。
| 内容类型 | 访问方法 | 所需权限 | 其他应用是否可以访问? | 卸载应用时是否移除文件? | |
|---|---|---|---|---|---|
| 应用专属文件 | 仅供您的应用使用的文件 | 从内部存储空间访问,可以使用 getFilesDir() 或 getCacheDir() 方法从外部存储空间访问,可以使用 getExternalFilesDir() 或 getExternalCacheDir() 方法 | 从内部存储空间访问不需要任何权限 如果应用在搭载 Android 4.4(API 级别 19)或更高版本的设备上运行,从外部存储空间访问不需要任何权限 | 否 | 是 |
| 媒体 | 可共享的媒体文件(图片、音频文件、视频) | MediaStore API | 在 Android 11(API 级别 30)或更高版本中,访问其他应用的文件需要 READ_EXTERNAL_STORAGE在 Android 10(API 级别 29)中,访问其他应用的文件需要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE在 Android 9(API 级别 28)或更低版本中,访问所有文件均需要相关权限 | 是,但其他应用需要 READ_EXTERNAL_STORAGE 权限 | 否 |
| 文档和其他文件 | 其他类型的可共享内容,包括已下载的文件 | 存储访问框架 | 无 | 是,可以通过系统文件选择器访问 | 否 |
| 应用偏好设置 | 键值对 | Jetpack Preferences 库 | 无 | 否 | 是 |
| 数据库 | 结构化数据 | Room 持久性库 | 无 | 否 | 是 |
如果用户数据可供或应可供其他应用访问,并且即使在用户卸载应用后也可对其进行保存,请使用共享存储空间。Android 提供用于存储和访问以下类型的可共享数据的 API:
具体公共目录内容说明:
DCIM/ 和 Pictures/ 目录中。系统将这些文件添加到 MediaStore.Images 表格中。DCIM/、Movies/ 和 Pictures/ 目录中。系统将这些文件添加到 MediaStore.Video 表格中。Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/ 和 Ringtones/ 目录中。此外,系统还可以识别 Music/ 或 Movies/ 目录中的音频播放列表,以及 Recordings/ 目录中的录音。系统将这些文件添加到 MediaStore.Audio 表格中。Recordings/ 目录在 Android 11(API 级别 30)及更低版本中不可用。Download/ 目录中。在搭载 Android 10(API 级别 29)及更高版本的设备上,这些文件存储在 MediaStore.Downloads 表格中。此表格在 Android 9(API 级别 28)及更低版本中不可用。通过以上资料的说明:APP需要下载的文件例如是图片、视频、音乐会保存放在DCIM、Movies 或者Movies 目录中;像PDF、APK等文件可以放在Download目录中,他们不需要进行对应权限的申请;例如在Android 13中保存apk文件使用 getExternalStoragePublicDirectory 如果不申请 WRITE_EXTERNAL_STORAGE权限会保存文件失败;如果我们只用官方说明的MediaStore API 来保存文件则不会遇到权限申请的问题。
示例代码1:
String destinationPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/app_update.apk";
示例代码2:
- public class ApkFileSaver {
-
- // 定义保存文件的目录和文件名
- private static final String DIRECTORY_NAME = Environment.DIRECTORY_DOWNLOADS;
- private static final String FILE_NAME = "my_app.apk";
-
- public static Uri saveApkFileToDownloads(Context context, byte[] apkData) {
- ContentResolver contentResolver = context.getContentResolver();
- ContentValues contentValues = new ContentValues();
- contentValues.put(MediaStore.Downloads.DISPLAY_NAME, FILE_NAME);
- contentValues.put(MediaStore.Downloads.MIME_TYPE, "application/vnd.android.package-archive");
- contentValues.put(MediaStore.Downloads.RELATIVE_PATH, DIRECTORY_NAME);
-
- Uri contentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
- Uri fileUri = contentResolver.insert(contentUri, contentValues);
-
- if (fileUri != null) {
- try {
- // 打开文件输出流并写入APK数据
- contentResolver.openOutputStream(fileUri).write(apkData);
- return fileUri;
- } catch (Exception e) {
- e.printStackTrace();
- // 处理保存文件时的异常
- }
- }
- return null;
- }
- }
参考文章:https://developer.android.com/training/data-storage/shared/media?hl=zh-cn