为什么在 Android 7.0+ 中应禁用 file:// 协议访问?

解读

国内面试官问这道题,并不是想听“官方文档说禁止”这种一句话答案,而是考察候选人是否真正踩过“拍照、裁剪、安装 APK”这类刚需场景的坑,是否理解 Android 安全边界从“沙箱”到“共享”演进的设计思路,以及能否给出可落地的整改方案。答不出“StrictMode + FileUriExposedException 触发点”、“targetSdk 24 前后差异”、“国内 ROM 魔改差异”这三层,基本会被判定为“只写过 Demo”。

知识点

  1. FileUriExposedException 触发条件:targetSdkVersion ≥ 24 且使用 file:// 向第三方 App 暴露文件。
  2. 7.0 安全模型:Google 强制启用 StrictMode 检测,任何把 file:// 直接塞进 Intent 让别的进程访问的行为都会立即崩溃,防止私有目录被任意读写。
  3. 国内魔改 ROM 差异:部分厂商把检测逻辑阉割掉,导致开发者在测试机没崩溃,上线到用户机(原生或海外 ROM)集中爆雷;面试时主动提这一点可体现“国内适配”经验。
  4. 替代方案:FileProvider 生成 content:// URI,配合 Intent.FLAG_GRANT_READ_URI_PERMISSION,一次性授权、用完即失效,符合最小权限原则。
  5. 降级“伪方案”:在 Application 里 disableDeathOnFileUriExposure 只能屏蔽 StrictMode 检测,无法通过国内应用商店安全扫描,也无法通过 Google Play Vulnerability 邮件,属于掩耳盗铃,面试中主动否定可加分。
  6. 关联考点:APK 安装、图片选择、相机拍照、裁剪、微信/QQ 分享、通知栏声音、自动更新,都是高频落地场景;能随口说出“在小米/华为/OPPO 上分别验证 FileProvider path 配置是否被缓存”会让面试官眼前一亮。

答案

Android 7.0 之后,系统在执行 StrictMode 检测时,一旦发现应用通过 Intent 把 file:// URI 暴露给第三方进程,会立即抛出 FileUriExposedException,直接崩溃。
根本原因是 file:// 绕过沙箱权限模型,接收方 App 默认拥有对目标文件的永久读写权,且无法撤销,存在任意代码执行与数据泄露风险。
官方推荐做法是使用 FileProvider 将私有文件生成 content:// URI,并配合 FLAG_GRANT_READ_URI_PERMISSION 做一次性临时授权,既满足共享需求,又符合最小权限原则,同时能通过国内商店和 Google Play 的安全扫描。
因此,在 targetSdkVersion ≥ 24 的项目里,必须彻底禁用 file:// 协议访问,全面替换为 FileProvider 方案,并在小米、华为、OPPO 等主流 ROM 上验证 path 配置与权限授予的兼容性。

拓展思考

  1. 如果业务强依赖第三方 SDK(如早期微信支付、某些人脸识别库)内部硬编码了 file://,而 SDK 方已停止维护,如何在不降级 targetSdk 的前提下兜底?
    思路:在进程启动早期反射关闭 StrictMode 检测,同时把相关文件提前迁移到外部共享目录并追加 .nomedia,配合自定义 FileProvider 做二次转发;但需在隐私合规评估中说明风险,并准备被应用商店下架的 B 计划。
  2. 折叠屏、多窗口场景下,FileProvider 授权的生命周期与 Configuration Change 不同步,可能导致跨窗口 Intent 复用旧 URI 而触发 SecurityException,如何加固?
    思路:在 Activity 或 Fragment 的 onCreate 阶段重新生成 URI,并使用 ClipData 替代 Intent.putExtra,让系统感知到这是一次全新的授权链;同时把授权有效期缩短到 5 秒以内,降低重放攻击窗口。
  3. 车载与 Wear 设备上,系统把 FileProvider 的 path 配置缓存到系统服务进程,OTA 升级后缓存未失效,导致升级后首次调用相机直接崩溃,如何灰度规避?
    思路:在每次版本升级后首次启动时,主动发送一个带 FLAG_GRANT_PREFIX_URI_PERMISSION 的 dummy Intent 给系统,强制刷新缓存;同时通过埋点统计崩溃率,按车型/设备维度动态关闭相机入口,待缓存重建后再开放。