安卓11+存储权限踩坑实录:BilibiliCacheVideoMerge双模式Path/Uri缓存文件访问方案完整对比
【免费下载链接】BilibiliCacheVideoMerge🔥🔥Android上将bilibili缓存视频合并导出为mp4,支持安卓5.0 ~ 13,视频挂载弹幕播放(Android consolidates and exports the bilibilibili cache video to mp4, supports Android 5.0~13, and plays the video on the screen)项目地址: https://gitcode.com/gh_mirrors/bi/BilibiliCacheVideoMerge
这篇文章以开源项目 BilibiliCacheVideoMerge(B站缓存视频合并导出 mp4 工具)为例,用一次真实的"踩坑实录"讲清楚:在安卓 11+ 的存储权限新规则下,它如何通过Path 直接路径模式与Uri 文件访问模式双方案设计,绕过限制读取 B 站缓存目录。
为什么"读文件"在安卓11上会翻车?
B 站缓存的视频并非一个 mp4,而是一套分片文件,目录结构大致是:
缓存根目录 └── 合集目录(如:某番剧) └── 章节目录(第1集 / 第2集...) ├── audio.m4s ← 音频 ├── video.m4s ← 视频 ├── entry.json ← 元信息(标题、封面、BV号) └── danmaku.xml ← 弹幕在安卓 10 及以前,应用拿到存储权限后,用File.listFiles()就能直接遍历这套目录。但**安卓 11(API 30)引入范围存储(Scoped Storage)**后,/storage/emulated/0/Android/data/...这类沙盒目录被严格隔离——B 站缓存恰好就住在这里,第三方应用再用传统 File API 直接访问,结果就是:
listFiles()返回null或空数组- 主页一片空白,缓存列表刷不出来
- 部分机型直接抛权限异常
这就是本项目要解决的核心矛盾:合并 B 站缓存视频,前提是先"读得到"缓存目录。
坑点现场:Path 模式的"失效边界"
Path 模式的实现在 PathCacheFileManager.kt:用File.listFiles()逐层遍历"合集 → 章节",再由 FileTool.getNeedPath() 按文件名识别出audio.m4s、video.m4s、entry.json、danmaku.xml四件套,缺失时通过 needSrcErrorHandle() 给出"xx 下没找到 video.m4s"这类友好提示。
在安卓 11+ 上,这条链路一旦触碰Android/data目录就失效——不是代码写错了,而是系统不再放行。于是作者没有去"硬刚"权限,而是为 Uri 通道做了一套完整平行实现。
双模式架构:Path 与 Uri 全对比
两种模式都实现同一个接口 ICacheFileManager.kt,公共逻辑(全选、刷新、加载弹窗等)收敛在 BaseCacheFileManager.kt 中,是教科书式的策略模式:
| 对比维度 | Path 直接模式 | Uri 文件访问模式 |
|---|---|---|
| 适用场景 | 安卓 10 及以下,或缓存路径不在Android/data | 安卓 11+ 且路径落在Android/data沙盒内 |
| 核心 API | java.io.File.listFiles() | SAF 的DocumentFile+ContentResolver |
| 授权方式 | 传统存储权限 | SAF 目录选择 + 持久化授权 |
| 遍历速度 | 快 | 慢(叠加 MMKV 缓存 + 进度弹窗兜底) |
| 核心实现 | PathCacheFileManager.kt | UriCacheFileManager.kt |
两条流水线在功能上完全对称:
- Path 模式:FileTool.getNeedPath() 收集文件的绝对路径
- Uri 模式:UriTool.getNeedUri() 收集文件的content Uri
每个缓存条目都会通过CacheFile实体中的useUri标记记明"我属于哪种模式",后续合并流程据此走对应分支。
应用如何自动选择模式?
模式不是让用户手动切换的,而是根据路径特征自动判定。在 MainFileShowFragment.java 中:
每次刷新列表前,先用
FileTools.underAndroidDataUseUri(path)判断缓存路径是否位于Android/data之下——是则走uriCacheFileManager,否则走pathCacheFileManager。
也就是说:老系统/普通目录 → Path;新系统/沙盒目录 → Uri,用户全程无感知。
Uri 模式的两大成本,作者如何补上
① 授权成本:SAF 必须用户点头
Uri 模式依赖 SAF(Storage Access Framework),权限要通过系统目录选择器授予。UriTool.grantedUriPermission() 会先检查目标 Uri 是否已有持久化授权,没有就弹出"授权提示"对话框,引导用户跳转系统授权页。该检查在 MainActivity 的onStart中每次拉起,防止授权失效后静默失败。
② 速度成本:DocumentFile 遍历非常慢
SAF 的listFiles()每调用一次都要走 ContentProvider,大缓存目录下会卡得明显。作者的三板斧:
- 进度反馈:遍历过程中弹窗实时显示
3 / 128这类进度(UriCacheFileManager.kt); - MMKV 结果缓存:getCacheMsgByMMKV() 把"章节 → 四件套 Uri + 标题/BV号"的解析结果按 Uri 为 key 存入 MMKV(分钟级过期),二次进入几乎零开销;
- 流式转存:合并前由 documentFile2File() 通过
openInputStream(uri)把 content Uri 流式写成本地临时 File(1KB 缓冲、每 40KB 回调一次进度),交给 FFmpeg 合并,MergeProgressDialog.java 再按useUri标记选择对应的展平逻辑。
顺带一提,合并完成后的 mp4 分享也踩过权限坑:安卓 7.0+ 直接Uri.fromFile()会抛FileUriExposedException,所以 FileTool.shareFile() 在 SDK ≥ 24 时统一改走 FileProvider,配合 AndroidManifest.xml 中声明的com.molihua.hlbmerge.fileprovider。
总结:安卓11+存储访问的3条经验
- 别假设 File API 万能:安卓 11+ 下其他应用的
Android/data沙盒目录,直连读取基本无望,SAF/Uri 才是正路; - 路径特征 + 系统版本做自动分流:像本项目一样按
underAndroidDataUseUri()动态选路,比让用户手选更省心; - 用缓存和流式转存对冲 SAF 的性能税:MMKV 记结果、弹窗给进度、合并前流式落盘,慢也能用。
对于想合并 B 站缓存视频的普通用户,这套双模式设计意味着:无论你的手机是安卓 5 还是安卓 13,只要按弹窗提示完成一次授权,缓存视频就能正常列出并合并导出。
【免费下载链接】BilibiliCacheVideoMerge🔥🔥Android上将bilibili缓存视频合并导出为mp4,支持安卓5.0 ~ 13,视频挂载弹幕播放(Android consolidates and exports the bilibilibili cache video to mp4, supports Android 5.0~13, and plays the video on the screen)项目地址: https://gitcode.com/gh_mirrors/bi/BilibiliCacheVideoMerge
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考