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

2021-06-11 16:27:47 +08:00
 xloger

我司 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...

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

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

3349 次点击
所在节点    程序员
23 条回复
omysho
2021-06-11 16:37:06 +08:00
之前做过适配

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

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

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

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

@yujiang #11

@NSAgold #13

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

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

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

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

不过后续我也没在跟进了,公司太过压榨人,然后我就提桶跑路了。
xloger
2021-06-15 15:01:35 +08:00
@omysho #18 😂获得了安慰,原来不是我一个人这样...
rosu
2021-09-13 10:55:32 +08:00
@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 的来源嘛?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/782941

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX