解惑Android Scoped Storage

原文链接 Android Scoped Storage Puzzles

安卓对于文件存储这块,其实是相当混乱的,在早期的版本中对存储甚至是没有所谓的管理的,有多种方法可以操作文件存储,比如通过Java原生的方式(File/InputStream/OutputStream),以及通过ContentProvider的方式(openFile/openOutputStream)。文件的路径也是相当混乱的,有公共存储区域,App的沙盒,外部存储中的App沙盒,但其实整个文件系统中的文件都是可读的,除了系统目录,其他应用的沙盒(内部和外部)以外也都是可写的。这就造成了,安卓的文件系统特别的混乱,加之应用使用不规范,对存储造成极大的滥用,外部存储空间产生大量的垃圾文件,应用卸载后仍然存在,这也催生了一系列的清理软件。

[图片上传失败...(image-168093-1697460955326)]

但随着用户隐私问题越来越被重视,谷歌也开始收紧了存储权限管理。

存储权限的变迁史

强制动态申请权限

从Android 6.0(Android M, API 23)开始,存储权限除了在AndroidManifest中静态声明以外,还需要在运行时动态的申请,用户授权了后才可以使用。但这并没有本质的变化,只要用户授权了,就跟以前一样,所有文件可读,大量位置的文件也都可写,App依然可以随便乱搞。

Scoped Storage

后来到了Android 10 (Android Q, API 29)时提出了scoped storage的概念,并把权限进一步的收紧,仅留下几个公共的文件夹可写,其他地方均不可写。并且写媒体文件时(Image/Video/Audio),要用MediaStore API的方式,直接以Java的文件方式将不再可用。Android 10并未强制开启scoped storage,通过在Manifest中设置requestLegacyStorageManage=true,依然可以像以往那样搞,但到了Android 11时,就强制开启了scoped storage。
[图片上传失败...(image-3a7be1-1697460955326)]

强制Scoped Storage

到了Android 11(Android R, API 30)时,就强制了,只有应用自己的沙盒可自由读写。创建媒体文件必须 通过MediaStore API。只有少数公共区域可读可写(Documents/Downloads/Pictures)等,并且这些区域只有自己创建的文件是可见和可读的。这就导致了,应用能乱搞的区间相当有限了。

细分文件种类权限

到了Android 13(Android T, API 33),更加变态了,把原来的WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE权限细分为对具体文件种类的操作,拆分为Image/Audio/Video三种媒体文件类型的读写。

注意:只有当targetSdk=33时,并且运行的设备是Android 13时,才需要做权限的细分处理。也许后面的版本就会强制了,如果强制那无论taregtSdk是多少都会强制使用细分 权限。

存储问题的适配

前面简单的介绍了安卓存储权限的变化过程,重点还是要了解一下如何适配。因为现在已经是2023年了,Android 13(Android T),API 33也快要普及了,所以就直接总结下现在应该怎么做。

动态申请存储权限

对于现在来说,动态申请权限已经是必备的技能之一了,不用多说了。就是先在应用的入口处,或者将要使用存储的地方去申请

在manifest中声明requestLegacyExternalStorage=true

用MediaStore API来操作媒体文件(Image/Audio/Video)

首先要声明具体的权限,然后用MediaStore API来操作。

申请另外一个权限MANAGE_EXTERNAL_STORAGE

注意: 这个不管你的targetSDK是多少,只要目标运行设备是Android 11,就必须 这样做。

从Android 11开始,要想看到公共区域(Documents/Downloads)中的所有文件,还必须申请另外一个权限MANAGE_EXTERNAL_STORAGE。因为理论上来说,公共区域你只能看见你自己创建的文件就够了,想看所有文件就越权了。但对于有些应用来说,如文件浏览器,或者请记录阅读器来说,肯定要能看到所有的文件。这个权限需要再次单独申请,用户授权后即可以看到公共区域的所有的文件了。

需要注意,这个权限像常规权限那样去申请是没用的,它的状态永远是denied的,所以,逻辑要这样写:

  1. 先正常申请存储相关的权限
  2. 正常权限有了后,判断Environment.isExternalStorageManager(),如果 是false则需要再次申请另外一个叫做access all files的权限,通过ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION或者ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
       // after read/write permission granted
       if (!Environment.isExternalStorageManager()) {
            requestAllFilesAccess();
        }
        // ...
        
   private void requestAllFilesAccess() {
        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        try {
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Log.d(LOG_TAG, "Exception: " + e.getMessage());
        }
    }

需要注意,有可能会有Activity not found exception,注意try-catch下。

因为这个权限又会导致失控,只要申请了就可以在公共区域随意创建文件了,所以谷歌对这个权限管控也是比较严的。虽然你在研发测试阶段可以申请这个权限,但是在发布阶段,要给应用商店一个使用此权限的合理理由,否则Play store会拒绝上线的(当然,如果 不在Play Store上面发布也就无所谓了)。

合理使用公共区域

作为App,最应该做的就是从业务上区分文件的属性,然后合理的选择存储空间进行存储。对于App本身运行相关的文件,如配置,如算法的模型,如数据库,缓存和日志等等应用本身运行密切相关的文件,一定不可以放在公共区域,一定要放在应用自己的沙盒内(通过context.getFilesDir, context.getCacheDir, context.getExternalFilesDir, context.getExternalCacheDir, context.openFileOutput)等来读写,应用自己的沙盒也是属于App的自由空间,你可以随意玩。

对于用户生成的文件则要放在公共区域,比如说相机产出的图片和视频,编辑软件产生的文档,这些属于用户生成文件,它的所有权是用户的,可能会通过其他软件再次编辑或者分享,这些肯定要放在公共区域的。对于媒体类Image/Audio/Video则要使用MediaStore API的方式来创建和管理。对于其他文档类型,在获得了WRITE权限 后也可以在(Documents/Downloads)中创建和管理的,绝大多数情况下,这就够了。

总的来说,在用户隐私越来越被重视的今天,安卓现在存储管理策略在保护用户隐私的前提下是能够满足开发者使用的。

参考资料

原创不易,打赏点赞在看收藏分享 总要有一个吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容