-
照片、视频、音频这类媒体文件。使用MediaStore 访问,访问其他应用的媒体文件时需要READ_EXTERNAL_STORAGE权限。
-
其他目录,使用存储访问框架SAF(Storage Access Framwork)
所以在Android 10上即使你拥有了储存空间的读写权限,也无法保证可以正常的进行文件的读写操作。
适配
最简单粗暴的方法就是在AndroidManifest.xml
中添加 android:requestLegacyExternalStorage="true"
来请求使用旧的存储模式。
但是我不推荐此方法。因为在下一个版本的Android中,此条配置将会失效,将强制采用外部储存限制。其实早在Android Q Beta 3之前都是强制的,但为了给开发者适配的时间才没有强制执行。所以如果你不抓住这段时间去适配,那么今年下半年出了Android 11。。。直接开花~~
如果你已经适配Android 10,这里有个现象要注意一下:
如果应用通过升级安装,那么还会使用以前的储存模式(Legacy View)。只有通过首次安装或是卸载重新安装才能启用新模式(Filtered View)。
所以在适配时,我们的判断代码如下:
// 使用Environment.isExternalStorageLegacy()来检查APP的运行模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isExternalStorageLegacy()) {
}
这样的好处是你可以在用户升级后,能方便的将用户的数据移动至应用的特定目录。否则你只能通过SAF去移动,这样会非常麻烦。如果你要移动数据注意只适用于Android 10下,所以现在适配反而是一个好时机。当然如果你不需要迁移数据,那适配会更省事。
下面就说说推荐适配方案:
- 对于应用中涉及的文件操作,修改一下你的文件路径。
以前我们习惯使用Environment.getExternalStorageDirectory()方法,那么现在可以使用getExternalFilesDir()方法(包括下载的安装包这类的文件)。如果是缓存类型文件,可以放到getExternalCacheDir()路径下。
或者使用MediaStore,将文件存至对应的媒体类型中(图片:MediaStore.Images ,视频:MediaStore.Video,音频:MediaStore.Audio),不过仅限于多媒体文件。
下面代码将图片保存到公共目录下,返回Uri:
public static Uri createImageUri(Context context) {
ContentValues values = new ContentValues();
// 需要指定文件信息时,非必须
values.put(MediaStore.Images.Media.DESCRIPTION, “This is an image”);
values.put(MediaStore.Images.Media.DISPLAY_NAME, “Image.png”);
values.put(MediaStore.Images.Media.MIME_TYPE, “image/png”);
values.put(MediaStore.Images.Media.TITLE, “Image.png”);
values.put(MediaStore.Images.Media.RELATIVE_PATH, “Pictures/test”);
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
- 对于媒体资源的访问:比如图片选择器这类的场景。无法直接使用File,而应使用Uri。否则报错如下:
java.io.FileNotFoundException: open failed: EACCES (Permission denied)
比如我在适配项目中使用的图片选择器时,首先修改了Glide 通过加载File的方式显示图片。改为加载Uri的方式ÿ