Android Q 应用存储空间限制

应用存储空间限制

1、背景介绍

Android 相比于 IOS 来说,外部存储一直都是很混乱的,为了改变相应的这种生态,Android Q 版本对此进行了大量的整改,使用更加精细的划分模式来最大程度的给予App更高的隐私性。

1、适配范围

应用存储空间限制仅适用于以 Android Q 为目标的应用程序,(targetSDK = 28) 或者在运行Android Q的设备上新安装的应用。

当满足以下每个条件时,系统会将应用程序的文件访问权限置于兼容模式:

  • 1、您的应用针对Android 9(API级别28)或更低版本。
  • 2、您的应用安装在从Android 9升级到Android Q的设备上。

2、整改内容

1、每个应用程序持有私有文件的独立存储沙箱

每个App,Android Q 都会创建一个独立的存储沙箱,限制其他应用程序访问您的应用程序的外部存储设备上的文件。常见的外部存储设备是/sdcard,

该方案具有以下优点:

  • 需要更少的权限。应用程序沙箱中的文件对您的应用程序是私有的。因此,不再需要权限在外部存储中访问、保存自己的文件。(READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE)
  • 相对于设备上的其他应用程序,隐私性更强。没有其他应用可以直接访问您应用的独立存储沙箱中的文件。此访问限制使您的应用程序更容易维护沙盒文件的隐私。

注意:如果用户卸载了您的应用,则会清除隔离存储沙箱中的文件。(这点有点类似于 IOS)

获取 Android Q 为每一个 App 分享的独立存储沙箱,可以调用如下方法

1
Context.getExternalFilesDir(String type)

而type的取值为以下这几种

  • {@link android.os.Environment#DIRECTORY_MUSIC},
  • {@link android.os.Environment#DIRECTORY_PODCASTS},
  • {@link android.os.Environment#DIRECTORY_RINGTONES},
  • {@link android.os.Environment#DIRECTORY_ALARMS},
  • {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
  • {@link android.os.Environment#DIRECTORY_PICTURES}, or
  • {@link android.os.Environment#DIRECTORY_MOVIES}.

2、每个应用程序持有公共媒体的共享集合

每个App,Android Q 都会创建出一个公共的存储区域,供里面的文件独立于 App 之外,当 App 卸载以后,保留该文件,例如:通过 App 下载下来的音乐文件,在这个区域,当你的app需要修改和读取由该APP创建的文件,则不需要任何权限,而如果需要读取和修改其他App创建的文件,则需要申请权限。关于具体权限如下所示:

  • 读取 Photos 相关文件,则需要 READ_MEDIA_IMAGES
  • 读取 Videos 相关文件,则需要 READ_MEDIA_VIDEO
  • 读取 Music 相关文件,则需要 READ_MEDIA_AUDIO

关于上述,还有两个比较特殊的存在

  • There’s no permission for accessing the Downloads shared collection. Your app can access its own files in this collection. To access other apps’ files in this collection, however, you must allow the user to choose a file using the system’s file picker app.
  • If your app uses the Storage Access Framework, it doesn’t need to request these media-scoped permissions.
1、访问共享集合

APP 申请了相应的权限以后,可以使用 MediaStore API 来访问这些共享文件。

  • 对于 Photos 相关文件,使用 MediaStore.Images
  • 对于 Videos 相关文件,使用 MediaStore.Video.
  • 对于 Music 相关文件,使用 MediaStore.Audio.
  • 对于 Downloads 相关文件,使用 MediaStore.Downloads.

适配点 : 在 Android Q 版本之后,调用 System 提供的 getExternalStoragePublicDirectory()方法,只会返回该 APP 的独立沙盒目录,要完成适配,则要一直持有 READ_MEDIA_* 权限,然后更新你的存储框架即可。

要访问手机当中的媒体文件,请使用MediaStore检索该文件,然后通过相应的文件描述符就可以访问该文件

2、APP 文件写入共享集合

Android Q 版本上,当用户卸载 App 的时候,System 会清除 App 所包含的数据,(包含 system 提供给 APP 的独立存储沙箱),如果 APP 想在卸载 APP 后保存部分数据的话,就需要将文件保存在共享集合当中。如何将文件写入共享集合?

使用 MediaStore 集合中插入新行,使用以下方式填充其列

  • 1、为 DISPLAY_NAME 和 MIME_TYPE 列提供值
  • 2、您可以使用 PRIMARY_DIRECTORY 和 SECONDARY_DIRECTORY 列来影响文件在磁盘上的放置位置
  • 3、保持 DATA 列未定义。这样,该平台可以灵活地将文件保存在沙箱之外.
  • 4、使用API ContentResolver.openFileDescriptor()来读取或写入新创建的文件的数据

    但是,如果用户稍后重新安装了 APP ,则 APP 无法访问这些文件,情况类似于应用程序尝试访问其他应用程序文件的情况

3、访问其他应用创建的文件

要访问和读取其他应用程序已保存到外部存储设备的媒体文件,请完成以下步骤:

  • 1、权限
  • 2、使用ContentResolver对象查找并打开文件, 请关注(ContentResolver loadThumbnail 方法)
4、写入其他应用创建的文件

通过将文件保存到共享集合,您的应用程序将成为该文件的所有者。通常,只有当您是文件所有者时,您的应用才能写入共享集合中的文件。但是,如果您的应用程序充当特定用例的用户默认应用程序,您还可以写入其他应用程序拥有的文件

  • 1、如果您的应用是用户的默认Photo Manager应用,则可以修改其他应用保存到“ 照片和视频”共享集合中的图像文件。
  • 2、如果您的应用是用户的默认音乐应用,则可以修改其他应用保存到音乐共享收藏集的音频文件。

==注意:无论是默认的照片管理器还是音乐应用,您的应用都应保持正常运行。==

要修改其他应用最初保存到外部存储设备的媒体文件,请使用ContentResolver对象查找文件并进行就地修改。执行编辑/修改操作时,请捕获, RecoverableSecurityException以便您可以请求用户授予您对该特定项目的写入权限。

3、APP 访问媒体文件

1、访问照片 (注意事项)

Android Q 增加了一些功能,使用户可以更好地控制在外部存储上访问照片的方式。总所周知,在图片文件当中会包含一些 Exif 元数据,通过这些数据,可以访问关于照片的一些详细信息,例如图片的位置信息,而在 Q 版本之上,为了更加安全的保护用户的隐私,对这些 Exif 元数据做了一定的限制。

==如果在 Android 的 System Package Manager 当中。你的应用是默认的 Photos Manager 应用,那么 System会自动让你的应用访问这些照片的位置信息==

除了上述着一种特殊情况,默认其他的应用如果需要访问图片的位置信息,需要满足以下两种情况

  • 1、权限 ACCESS_MEDIA_LOCATION
  • 2、从MediaStore对象,调用setRequireOriginal(),传入照片的URI。

例如,以下代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Uri photoUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex));
final double[] latLong;
if (BuildCompat.isAtLeastQ()) {
photoUri = MediaStore.setRequireOriginal(photoUri);
InputStream stream = getContentResolver().openInputStream(photoUri);
if (stream != null) {
ExifInterface exifInterface = new ExifInterface(stream);
double[] returnedLatLong = exifInterface.getLatLong();
latLong = returnedLatLong != null ? returnedLatLong : new double[2];
stream.close();
} else {
latLong = new double[2];
}
} else {
latLong = new double[]{cursor.getFloat(latitudeColumnIndex), cursor.getFloat(longitudeColumnIndex)};
}

如果您的应用是相机应用,则它无法直接访问照片和视频共享集中保存的照片,除非它是设备的默认Photo Manager应用。要将用户定向到图库应用,请使用 ACTION_REVIEW意图

2、访问媒体文件 (注意事项)

应用需要访问特定的媒体文件,例如其他应用分享文件给该应用,或需要访问来自用户媒体集的文件。在这些情况下,优先获取文件的uri,然后通过 content resolver 获取文件描述符。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Uri contentUri = ContentUris.withAppendedId(android.provider.MediaStore.
Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd = null;
try {
parcelFd = this.getContentResolver().openFileDescriptor(contentUri, fileOpenMode);
if (parcelFd != null) {
int fd = parcelFd.detachFd();
// 获取到相应的文件描述符以后,即可以操作该文件。
}
} catch (FileNotFoundException e) {
e.printStackTrace();
// 错误处理(文件没有找到)
} finally {
// 清理操作
}
3 、访问特定文件

在某些用例中,您的应用可能需要打开或创建无权访问的文件:

  • 1、在照片编辑应用中,打开一张图纸。
  • 2、在业务生产力应用程序中,将文本文档保存到用户选择的位置。

对于这些情况,请使用存储访问框架,该框架允许用户选择要打开的特定文件,或选择特定位置来保存文件。

4、外部存储设备

在Android 9(API级别28)及更低版本中,所有存储设备上的所有文件都显示在单个”external”卷(主卷 – 类似于Windows上面的C盘)名称下。Android Q为每个外部存储设备提供唯一的卷名。此命名系统可帮助您有效地组织和索引内容,并使您可以控制新内容的存储位置。

要唯一标识外部存储中的特定文件,必须同时使用卷名和ID。例如,

  • 主存储设备上的文件将是content://media/external/images/media/12,
  • 但是被调用的辅助存储设备上的相应文件FA23-3E92将是 content://media/FA23-3E92/images/media/12。

您可以通过将此卷名称传递到特定的媒体集合来访问存储在特定卷上的文件,例如 MediaStore.Images.getContentUri()。

1、获取外部存储设备列表

要获取所有当前可用卷的名称列表,请调用 MediaStore.getAllVolumeNames(),如以下代码段所示

1
Set<String> volumeNames = MediaStore.getAllVolumeNames(context);

3、测试

设置虚拟外部存储设备

在没有可移动外部存储的设备上,使用以下命令启用虚拟磁盘以进行测试:

1
adb shell sm set-virtual-disk true

为了帮助您使应用程序与此新行为更改兼容,该平台提供了多种方法来调整与更改相关的多个参数。

切换行为更改
要在Android Q Beta 1中启用此行为更改,请在终端窗口中执行以下命令:

1
adb shell sm set-isolated-storage on

运行此命令后,设备将重新启动。如果没有,请等一下再尝试再次运行该命令。

要确认行为更改已生效,请使用以下命令:

1
adb shell getprop sys 。isolated_storage_snapshot

测试兼容模式行为
测试应用程序时,可以 通过在终端窗口中运行以下命令来启用外部文件存储访问的兼容性模式:

1
adb shell cmd appops设置your-package-name android:legacy_storage allow

要禁用兼容模式,请在Android Q上卸载并重新安装您的应用,或在终端窗口中运行以下命令:

1
adb shell cmd appops设置your-package-name android:legacy_storage default

4、特性总结

    1. Android Q 为每个应用程序在外部存储设备提供了一个独立的存储沙箱,应用通过路径创建的文件都保存在应用的沙箱目录
    1. 共享集合:如果应用的一些文件是用户选择下载保存的,应用卸载的时候用户不希望删除,这部分文件开发者可以通过MediaProvider接口保存在公共共享集合,包括:照片、视频、音乐和下载集合
    1. 新的访问多媒体文件的权限:应用读写自己创建的文件不需要权限,但是如果需要读取其他应用创建的多媒体文件就需要申请对应的权限,通过MediaProvider接口读取
    1. 读写其他应用的下载公共集合文件需要使用SAF的方式读写
    1. 目前版本该特性没有默认开启,需要开发者通过命令开启:adbshell smset-isolated-storage on
    1. 参考谷歌提供的官方适配指导文档:https://developer.android.google.cn/preview/privacy/scoped-storage