Scoped Storage ( Beta 3 起至今)
官方文档 中的 "An app that uses scoped storage always has read/write access to the files that it creates, both inside and outside its app-specific directory." 这句话相当容易误解。其中的 "outside its app-specific directory" 并不是指应用可以像以前有存储权限时一样可以任意读写内置存储,而只是应用可以通过 Media Store 和 Storage Access Framework 在其专有文件夹以外建立文件并可任意访问。
简而言之,使用 Scoped Storage 的行为如下:
- 直接访问文件:只可访问
Android/data/<package>Android/media/<package> - 读取媒体(照片、影片、音乐):使用 Media Store,获取后使用
getContentResolver().openInputStream打开文件 - 插入 /删除媒体:使用 Media Store,文档并未提及
- 访问任意文件:使用 Storage Access Framework
使用 Media Store 插入图片的例子
private boolean insertImage(File image) throws IOException { ContentValues values; // 向 Media Store 插入标记为待定的空白文件 values = new ContentValues(); values.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "test"); // 不同类型文件可用 RELATIVE_PATH 不用,具体请参阅 MediaProvider 源码 values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, Long.toString(System.currentTimeMillis())); values.put(MediaStore.Images.ImageColumns.IS_PENDING, true); Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri == null) { return false; } // 写入文件内容 InputStream is = new FileInputStream(image); OutputStream os = getContentResolver().openOutputStream(uri, "rw"); byte[] b = new byte[8192]; for (int r; (r = is.read(b)) != -1; ) { os.write(b, 0, r); } os.flush(); os.close(); is.close(); // 移除待定标记,其他应用可访问该文件 values = new ContentValues(); values.put(MediaStore.Images.ImageColumns.IS_PENDING, false); return getContentResolver().update(uri, values, null, null) == 1; } “沙盒”(只存在于 Beta 2,Beta 5 起删除相关代码)
Google 的“沙盒”的做法和 Rikka 的 Storage Redirect 核心部分原理相同,即使用挂载将一个只属于应用的文件夹(Android/sandbox)挂载为内置存储根目录,这样应用就只能访问该文件夹而不能访问真实的内置存储。
但这样做显然会产生一些问题(包括但不限于):
- 访问媒体文件:媒体存储返回假路径(
/mnt/media开头),在应用进程 hook 相关 IO 函数,如果是假路径就使用通过 content provider 获取到的远端 fd - 保存媒体文件:使媒体存储增加扫描
Android/sandbox - 跨挂载点移动文件:Google 未解决
Google 在 Beta 2 的“沙盒”使用和 Rikka 一样的思路,即在最小化影响的前提下实现隔离应用产生的文件的功能。但结果是大部分用户根本不 care 有没有隔离(毕竟喜欢乱吐文件的应用几乎都来自中国大陆),只会关心自己要使用的应用有没有炸裂。
个人觉得对 Google 来说,于其使用这样相对激进的方案到头来吃力不讨好,倒不如选择之后的更简单的 Scoped Storage,并给应用一个大版本的时间进行适配,因为毕竟哪种方案都需要应用开发者做出相同的适配工作。<del>然而现在文档既模糊又不全(</del>
个人感慨
应用滥用存储权限的问题自古以来就是一个大问题,Rikka 个人从小时候(划掉)起就一直想解决这个问题。所以 Rikka 从 2016 年底开始创造 Storage Redirect,至今已经有相当高的完成度,并且对各种问题都有相应的解决方案,只要使用者愿意稍微动一动脑子就可以获得相当好的体验。
2019 年,Google 终于愿意开始解决这个问题,根据 Rikka 个人的实验和体验,绝大部分应用需要做的工作并没有很多,只是目前 Google 的文档过于模糊和残缺。
只要 Google 在 Android R 时能真正推行所有应用必须作出改变,那 R 到来的一两年世界后将会变得更美好(划掉)。(当然如果有应用滥用 Storage Access Framework 的授权访问整个存储 Rikka 魔法还是有用的(划掉
对某些过于乐观的人的疑惑(
从 Beta 2 起,就不断地听到有人说“啊,有了 Q 就没有应用乱吐文件的问题啦”,Beta 2 之后砍掉沙盒也有人哀嚎“啊,怎么砍了”。然而即使保留,除非所有应用都进行适配,否则必然是无尽的“找不到文件”“打不开文件”。国内大厂的适配时间都是以年计算(想想 QQ WeChat 今年才支持接收 content uri ),所以即使予以保留,也肯定是要等待至少一两年后才能有比较好的体验(
