不是我不想支持 Android 10 的分区存储啊 Orz - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
请不要在回答技术问题时复制粘贴 AI 生成的内容
xloger

不是我不想支持 Android 10 的分区存储啊 Orz

  •  
  •   xloger Jun 11, 2021 3973 views
    This topic created in 1780 days ago, the information mentioned may be changed or developed.

    我司 App 最近终于打算上架 Google Play 了,因此在做一些准备工作。Google Play 的上架要求是 targetSdkVersion 最低 29,也就是要适配分区存储 /存储隔离。 当然,还有一种临时的方案就是在 application 设置 android:requestLegacyExternalStorage="true"。不过这显然也不是啥长久之计,我决定还是直接支持为好。

    说一下 App 情况:我们本身行为是很干净的,只有在 DCIM 下创建了一个我们的文件夹,用于用户导出的视频。然后再有一个内置的图片 /视频 /音频选择器,供用户导入,预览内容。

    我看了一通 Android 自己的 Android 10 兼容文档,和一些相关的适配文章,<del>很快就适配好了</del>。

    大致思路是,我们没法直接操作非我们私有目录文件的 File 了,我们能得到的是 Uri,然后需要读就调 contentResolver.openAssetFileDescriptor 之类的方法,操作 FileDescriptor 即可。 看着是不是很简单!我也觉得!

    然后我就愉快地开始测试了=。=

    然后发现,项目用到的一个第三方框架,一款获取视频某个关键帧画面的框架 FFmpegMediaMetadataRetriever,在用 FileDescriptor 解析一部分视频时,会发生 Native Crash 。我想着人家是开源项目嘛,我自己琢磨下咋修呗,然后研究了半天,也试了不同的场景,甚至还找到了当年该作者关于这个问题的提问贴,但看了一圈感觉写得没问题啊...

    最后放弃了,给作者提了个 Issue 。

    我把用到该框架的功能封装了一下改用 Android 自己的 MediaMetadataRetriever 实现了,姑且算是规避了这个问题。

    然后,过了两天,我又发现,我如果反复加载视频(对应的用户操作就是在项目列表页反复进入编辑页退出),以前的操作是直接调系统的一个接口 MediaExtractor.setDataSource(filePath),没有问题。但是兼容 Android 10 传 uri 后,操作就是先 contentResolver.openAssetFileDescriptor(uri, "r"),再 MediaExtractor.setDataSource(fileDescriptor),然后当操作次数多了后,openAssetFileDescriptor 就有概率阻塞住,需要过很久才会响应。

    我依旧对其进行了很久的分析,自然是有 close 的,而且它的几个不同的 setDataSource 函数本质上也是互相调用的,折腾了一圈,最终结论就是似乎不是我的锅...

    而且这个只在一部分手机上会出现。比如我自己的小米 11 就正常。 暂时无解。

    再然后,今天早上,测试小姐姐又跟我说,在一台测试机上导图片又会 Crash 啦。我看了一下,大致原因是我们的 Gif 支持是用的一个第三方框架 android-gif-drawable,它在 Android 11 上(也可能是一部分 Android 11 手机)如果传入一张非 Gif 图,会产生 Native Crash 。

    我们可以选择先判断 exif 信息之类的,只对 gif 调用。或者再给作者提个 Issue...

    所幸我报着试一试的态度更新了库的依赖,解决了问题。而这个版本也是近几个月才更新修复的。

    _(:з)∠)_我已经写得身心俱疲了...不知道哪里会不会又有新的兼容性问题,好烦啊......

    23 replies    2021-09-13 15:28:35 +08:00
    omysho
        1
    omysho  
       Jun 11, 2021 via Android
    之前做过适配

    简单的来说,直接 request legacy 即可!

    原因:
    Android 11 恢复了媒体文件的 File API

    所以在 Android 11 是可以直接读写媒体文件的,只不过只能读写 正规媒体目录 的 媒体文件。
    secretman
        2
    secretman  
       Jun 11, 2021
    第三方建议自己修改,我以前用知乎开源的 Matisse 做图片选择,后来升级 Android 版本适配不支持,就自己写了一个
    xloger
        3
    xloger  
    OP
       Jun 11, 2021
    @omysho #1 我看文档的说法是 Android 11 开始就会忽略了 `android:requestLegacyExternalStorage` 属性,所以我是打算最后不行了才开这个凑合一下。
    然后 Android 11 恢复了 File API 这事,这个就是我傻逼了。这个说法我是之前就知道的,然后,当时我正在解决上面说的 `FFmpegMediaMetadataRetriever` 的 Crash 问题,这期间我尝试了各种做法,包括把 targetSdkVersion 升级到了 30,再给它传 filePath,依然不行。就给我留下了 File API 对我依旧不好使的印象。
    刚刚看到你的回帖,对我描述的第二个问题 `MediaExtractor`,换回 filePath 了,目前没有复现用 fileDescriptor 的阻塞 bug,真是喜大普奔。Android 这也真是的 Orz
    xloger
        4
    xloger  
    OP
       Jun 11, 2021
    @secretman #2 一般的第三方框架还好,但我这次有问题的很多是涉及到 JNI 的,我试过了没改动_(:з)∠)_我至今仍不知道 `FFmpegMediaMetadataRetriever` 的作者代码是哪里写错了...
    Jirajine
        5
    Jirajine  
       Jun 11, 2021 via Android
    盲猜 fd/uri 失效,通过文件选择器授权的访问应该是临时的,可能用户切出去再回来就不行了。
    gif 那个应该不算坑,你自己实现文件访问也要判断,而且你传个 image/gif 的 mime type 也可以避免未预料的文件类型。
    ho121
        6
    ho121  
       Jun 11, 2021 via Android
    openAssetFileDescriptor 这个是读取 assets 文件夹下文件的吧?对于从 saf 拿到的 uri,好像是用 openInputStream 和 openOutputStream 吧
    xloger
        7
    xloger  
    OP
       Jun 11, 2021
    @Jirajine #5 我访问的资源都是在公共目录的,不需要用户手动授权,所以应该是不存在失效问题的吧?我之前怀疑的是频繁读取释放后,可能哪里出错了导致文件处于被占用状态,然后我再访问就一直在等解除占用了。一个体现是在阻塞的时候如果等个两三分钟,那还是能正常加载出来的。我本来还是想 debug 一步一步看是内部具体哪个函数卡住了,但是又被其他事情拖住了。
    Gif 这个的确有我一部分问题,之前我担心自己判断类型不准确,或者用户某些杂七杂八的 Gif 格式不对,就 try catch 了这个框架的构造方法,如果没出错且获取帧数大于 1 则认定是 Gif 。之前这样做是没问题的。换成了 fd,在大部分手机上也是没问题的,但是一部分手机就会 Native Crash 了,我也没法捕获。
    xloger
        8
    xloger  
    OP
       Jun 11, 2021
    @ho121 #6 我开始也是这样以为的,但其实并不是。而且在 `openFileDescriptor` 的注释中也提到了 "If at all possible,you should use {@link #openAssetFileDescriptor(Uri, String)}."虽然本质上它只是封了一层。

    我们还没支持通过 SAF 获取文件,目前是通过 MediaStore 获得的,然后视频播放我用到的 `MediaExtractor` 和 `MediaMetadataRetriever` 的 `setDataSource` 是不支持流的方式,只有 File 和 FileDescriptor 两套。
    当然,迫不得已,我也是可以通过 Uri 开个流把用户选择的音视频保存在自己私有目录。但这样就太怪了,而且显得仿佛偷用户隐私一样...不过如果要做 SAF 支持,应该只能这样做了吧...
    Jirajine
        9
    Jirajine  
       Jun 11, 2021 via Android
    @xloger 我理解的是通过文件选择器 UI 打开一个文件,就是授权你打开该文件一次而已。我用的一些应用通过 intent 分享一个文件 URI,原应用被杀 URI 就失效了。你要是需要多次访问,那应该读进内存或者存到自己的 cache 里。
    等很长时间能正常加载听起来像 gc 回收 finalize 或后台进程被 Android 杀掉释放了资源。可能你哪里有内存泄漏或 data race,或者没有在退出的时候正确释放。
    你用的这个 gif 库可能没有妥善检查,给了错误的数据原生代码有 ub 也正常,还是最好自己先检查,并且系统本身提供 MIME type 的 api,也不用自己实现。
    dingwen07
        10
    dingwen07  
       Jun 11, 2021 via iPhone
    为什么不用 Documents UI
    yujiang
        11
    yujiang  
       Jun 11, 2021 via Android
    为啥不用系统内置的的文件选择啊
    john6lq
        12
    john6lq  
       Jun 11, 2021 via iPhone
    我想想 v4 v7 v11 X Jetpack 就恶心,审美也是一言难尽。
    NSAgold
        13
    NSAgold  
       Jun 11, 2021
    为什么不用 Documents +1
    "文档应用是 Android 系统的一部分"
    xloger
        14
    xloger  
    OP
       Jun 12, 2021
    @dingwen07 #10

    @yujiang #11

    @NSAgold #13

    我们是剪辑 App,用户需要能预览,多选,预裁剪他们的素材,所以有一个内置的资源选择器是必然的。包括上面一直在提的,也不是选择器的内容啊,而是处理 Android 10 返回的 Uri 遇到的问题,这个靠 Documents UI 也是解决不了的。
    xloger
        15
    xloger  
    OP
       Jun 12, 2021
    @Jirajine #9 谢谢您的意见。我们这个场景并不是通过 文件选择器 UI ( SAF )获得的 Uri,而是通过系统的 MediaStore 扫描公开多媒体库得到的,比如 DCIM 、Pictures 这些文件夹里的。按我的理解,这些 Uri 是没有权限问题的(或者说我申请了 Read Video 权限,就一直拥有了)。
    内存泄漏和正确释放的这些问题,我觉得我没有,但的确我还没靠数据验证过,我到时候尝试写个最小复现 Demo 来验证下这个问题。
    GIF 我之所以为啥不看 MIME,可能是之前处理视频,被各种奇怪的错误信息坑怕了,有分辨率错的,有帧率不准的,也有啥都没有的,所以对于 GIF,我之前就想着反正我是用这个库解析,它能解出来就是 GIF,不能解出来就当不是吧=。= 不然遇到我认定是 GIF 的但是它解析不出来,还要处理额外的兼容问题。
    106npo
        16
    106npo  
       Jun 13, 2021 via Android
    想要完美兼容 SAF 就要学学 iOS 应用的做法,拿到 uri 立马往 cache 拷贝一份
    xloger
        17
    xloger  
    OP
       Jun 15, 2021
    最后我的解决方案如一楼所说,换回了 File 。白瞎我写好了一套 File Uri 兼容的接口。

    我在最开始做兼容时,想着是遵循规范,所以尽管可以开 `requestLegacyExternalStorage` 凑合一下,我也没有选择这个。然后自己弄好了所有对 Uri 的支持,并且一部分自己的私有文件还是得用 File,也做好了相关的处理。

    然后就遇到了主楼所说的各种兼容性问题。我知道 Android 11 恢复了 File API,但是在我的测试机( Android 10 )上还是不行,这是显而易见的因为我测试机还是 10 嘛,而我不可能不对 10 做兼容。

    =。=然后我发现我傻逼了,还有 `requestLegacyExternalStorage` 啊,这个 API 只在 Android 10 生效。

    所以,解决这个破问题的完美方案就是,继续用 File 那套,开启 `requestLegacyExternalStorage`。target SDK 用 29,30 都可以。去 TM 的 Uri,去 TM 的 FileDescriptor,一堆问题还让我用。

    我本将心照明月,奈何...

    ps:我本来想去验证一下 `openFileDescriptor` 阻塞这个问题真不是我的锅的。用完 FileDescriotor 我就 close 了,MediaMetadataRetriever 用完我也 release 了,我还要干啥啊。不过马上就要赶下一个需求了,先鸽了吧。
    omysho
        18
    omysho  
       Jun 15, 2021
    @xloger #17

    其实我也走过你的这个坑,当时公司要求我做 Android 10 的适配,当时也是类似你这么做

    但是当时公司有 native 的音视频相关的库,测试倒是没发现什么坑,但是一进行 beta 上线就发现一堆问题。

    然后我就发现了 Android 11 恢复了 File API,当时我就感觉自己是个大 SB,连忙把所有东西全部回退,然后 requestLegacy 就完事了。

    不过后续我也没在跟进了,公司太过压榨人,然后我就提桶跑路了。
    xloger
        19
    xloger  
    OP
       Jun 15, 2021
    @omysho #18 获得了安慰,原来不是我一个人这样...
    rosu
        20
    rosu  
       Sep 13, 2021
    @xloger 所以 Google 又改回去了嘛? Android 11 的存储机制变更文章( https://developer.android.com/about/versions/11/privacy/storage )我看了好多遍,自认为没有看漏,就是不支持 `requestLegacyExternalStorage `:

    > If your app targets Android 11, both the WRITE_EXTERNAL_STORAGE permission and the
    > WRITE_MEDIA_STORAGE privileged permission no longer provide any additional access.



    结果真机一测试,发现 it juts works ?!


    我现在有点懵,能请求下关于恢复 File API 的来源嘛?
    xloger
        21
    xloger  
    OP
       Sep 13, 2021
    @rosu #20 我之前也是看官方文档的,所以也有类似你的误解。Android 11 的确不支持 requestLegacyExternalStorage 啊,但是这没影响。
    对于 Android 10,我们申请了 requestLegacyExternalStorage,所以一切能正常使用。
    对于 Android 11,requestLegacyExternalStorage 失效了,但是恢复了 File API 的支持,一样能正常使用。

    我负责的 App 自身行为是很规范的,信息存储在私有目录,唯一的问题在于有个内置多媒体选择器,这部分获取了相关权限后照旧用 File 操作就好了,不需要变动。
    rosu
        22
    rosu  
       Sep 13, 2021
    @xloger File API 指的是 「 File 相关的 API 」 对吗?那我理解错了。我一开始以为是外部存储相关的东西呢...

    我纠结的地方在于:

    1. Android 10 必须启用 `requestLegacyExternalStorage`,才能使用 `WRITE_EXTERNAL_STORAGE` 权限
    2. Android 11 (事实上)却又直接支持 `WRITE_EXTERNAL_STORAGE` 权限,但文档里又写了不支持...所以我就有点懵
    xloger
        23
    xloger  
    OP
       Sep 13, 2021
    @rosu #22 File API 指的是 「 File 相关的 API 」 ,是的。

    Android 11 对 WRITE_EXTERNAL_STORAGE 的支持我忘了,我是规范地获取读取了多媒体权限,然后只有一个写入视频的权限,所以这个对我没影响。如果你操作的是 SD 卡里系统相关的几个目录,那能操作是正常的,如果不是,那我也不太清楚了。(这些文档看完我就忘记得差不多了,它自己反复横跳搞不清...)
    About     Help     Advertise     Blog     API     FAQ     Solana     3608 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 58ms UTC 04:44 PVG 12:44 LAX 21:44 JFK 00:44
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86