为什么两个应用即使安装在同一设备上也无法直接访问对方的私有文件?

解读

面试官想确认候选人是否真正理解 Android“沙箱”机制的本质,而不仅仅是背出“权限”两个字。国内厂商对 ROM 深度定制,但沙箱底线未被打破,因此答案必须围绕 Linux UID/GID、SELinux、安装时签名、文件系统节点权限四点展开,并指出“同一设备”≠“同一沙箱”。同时,面试官会追问“有没有例外”,需要候选人主动把 FLAG、FileProvider、sharedUserId(已废弃)等边界情况一次性讲清,体现对国内生态(如微信文件接收、钉钉分享)的落地经验。

知识点

  1. 应用级沙箱:安装时 PackageManager 为每个 APK 分配唯一 UID(app_×××),内核层通过 UID/GID 隔离 data/data/包名/ 目录,chmod 700,其他 UID 无法进入。
  2. SELinux MAC:即使 root 篡改 UID,SELinux policy 仍禁止 domain=appdomain 的进程访问非本包目录,国内 ROM(MIUI、EMUI、ColorOS)全部强制 enforcing。
  3. 签名与 sharedUserId:只有相同签名且 AndroidManifest 中声明相同 sharedUserId 的两个应用才能运行在同一 UID,Android 10 起官方废弃,国内新装应用已无法使用。
  4. 文件访问唯一官方通道:
    • 私有文件:Context.getFilesDir(),700 权限
    • 共享文件:MediaStore、SAF、FileProvider(exported=false,通过 Intent 授予临时 URI 权限),国内主流 App 分享均走 FileProvider。
  5. 国内特殊情况:
    • 微信“外部存储专属目录”/sdcard/Android/data/包名/ 仍受 UID+SELinux 保护,第三方扫码工具无法直接读取。
    • 部分厂商“换机助手”系统级白名单使用 signature|privileged 权限,但普通 SDK 应用无法获得。

答案

“Android 在 Linux 内核层为每个应用分配独立 UID,并在/data/data/包名/ 目录设置 700 权限,确保非同一 UID 的进程无法进入;同时 SELinux 强制策略把每个应用标记为独立的 appdomain,禁止跨域访问。即使两个 APK 安装在同一台设备,只要 UID 不同、签名不同,就无法突破内核与 SELinux 的双重隔离。官方唯一推荐的跨应用文件共享方式是 FileProvider,通过 Intent 临时授予 URI 访问权限,国内微信、钉钉等也都是这样实现。sharedUserId 方案虽能让同签名应用共享 UID,但 Android 10 已废弃,国内商店新包不再支持。因此,私有文件默认对任何第三方应用都是不可见的。”

拓展思考

  1. 如果业务强依赖“零点击”批量传输大文件,可考虑自定义 ContentProvider 并声明 android:exported="true",通过签名级自定义权限保护,避免弹窗,但需在国内各大商店审核时提供安全评估报告。
  2. 在车载或 Wear 等全场景设备上,系统级应用可使用 signature|privileged 权限调用 hidden API 直接创建共享目录,但普通应用无法获取平台签名,因此仍需回归 FileProvider + URI 授权模型。
  3. 未来 Android 14 将进一步收紧“外部存储”访问,targetSdk=34 默认不返回真实路径,需提前适配 SAF 或 MediaStore,否则国内主流机型(小米 14、Mate 60 系列)上将直接抛 SecurityException。