如何通过 sharedUserId 实现多个应用共享同一 UID?存在哪些风险?

解读

面试官想确认两点:

  1. 你是否真的动手配过 sharedUserId,还是只背过概念;
  2. 你对 Android 多应用隔离模型、签名机制、权限边界、升级兼容性的理解深度。
    国内场景下,很多中小厂为了“插件化”“用户体系打通”会尝试 sharedUserId,但真正上线后被“升级失败”“权限爆炸”“安全合规审计”打回的案例极多,所以面试官希望听到“落地经验 + 风险清单 + 替代方案”。

知识点

  1. 应用 UID 与进程沙箱:安装时 PMS 通过 PackageParser 解析 APK,未声明 sharedUserId 则分配唯一 UID;若声明且签名一致,则复用已有 UID。
  2. 签名一致性校验:系统只比对「签名证书指纹」,不比对 v1/v2/v3 方案细节;国内厂商某些魔改 ROM 会额外校验签名算法 OID,导致同证书仍失败。
  3. 权限继承:sharedUserId 应用获得同一 GID、同一 SELinux 域,可相互访问私有数据目录 /data/data/<pkg>/、共享 SharedPreference、数据库、甚至 native 代码段。
  4. 升级陷阱:已安装 APK 无 sharedUserId,后续版本追加,系统拒绝安装(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE);反之去掉 sharedUserId 也会失败。国内渠道分发时,若用户通过应用商店增量更新,商店往往直接拉取最新 APK 做全量替换,失败率更高。
  5. 国内合规:工信部 164 号文、26 号通报把「超范围收集」列为重点;sharedUserId 共享目录后,若 A 应用把用户数据写入公共区,被 B 应用读取,极易被检测为「未明示共享」而通报下架。
  6. 替代技术:ContentProvider+signature 级自定义权限、AIDL+in-app Service、Broadcast+signature 权限、FileProvider+FLAG_GRANT_READ_URI_PERMISSION、Binder RPC 封装 SDK、动态交付(Dynamic Feature)等。

答案

步骤:

  1. 在所有需要共享 UID 的 APK 的 AndroidManifest 根节点添加同一属性:
    android:sharedUserId="com.example.foo"
    字符串值任意,但必须在同一设备全局唯一,建议用公司反域名。
  2. 使用完全相同签名证书对 APK 进行签名;v1+v2 同时打开,避免部分 ROM 只验 v1。
  3. 首次安装时,系统 PMS 在 Settings#mSharedUsers 中记录 sharedUserId→UID 映射;后续 APK 只要签名一致即复用该 UID。
  4. 代码层即可通过 createPackageContext("sister.pkg", CONTEXT_IGNORE_SECURITY) 获取对方 Context,直接 openFileInput、getDatabasePath、甚至 ClassLoader 加载代码。

风险清单(面试时逐条展开,体现踩坑经验):

  1. 升级断裂:已发布版本未带 sharedUserId,后续无法追加;只能换新包名或强制清数据,导致用户流失。
  2. 权限膨胀:A 应用申请 READ_SMS,B 应用自动获得,隐私合规审计无法解释。
  3. 数据污染:共享目录下文件命名冲突、数据库版本升降级不一致,极易出现 Crash 或数据损坏。
  4. 安全放大:若其中一款应用被植入漏洞,攻击者可直接读写姐妹应用私有数据,横向扩展攻击面;国内 SRC 审核时会被认定为「高风险横向移动」。
  5. 系统限制:Android 10 以后共享库路径变化、Android 13 开始 SELinux 域进一步细化,部分 ROM 已出现 sharedUserId 应用无法访问 /data/app/…/lib 的 case。
  6. 未来废弃:Google 在 AOSP 13 标记 sharedUserId 为 deprecated,未来可能彻底移除;国内头部厂商已跟进提示「不建议使用」。

因此,线上项目若无历史包袱,应优先采用 signature 级自定义权限 + ContentProvider 方案;若已使用 sharedUserId,需提前规划「包名双开 + 数据迁移脚本」平滑退场。

拓展思考

  1. 如果三方 SDK 也用了 sharedUserId,如何检测冲突?
    答:在 CI 阶段写 Gradle 脚本解析 merged_manifest,扫描所有 sharedUserId 值,出现不一致即中断打包;同时解析签名证书,确保所有依赖模块使用同一证书。
  2. 国内某些运营商 ROM 对 sharedUserId 应用做「冻结」处理,导致无法后台拉起,如何兼容?
    答:在 AndroidManifest 中声明 android:sharedUserMaxSdkVersion="29" 让 30 以上设备不再共享,同时用 WorkManager+BatteryHistorian 验证后台存活率,低于阈值则提示用户手动关闭省电限制。
  3. 若必须共享 native 层 so,而 sharedUserId 被禁用,有何替代?
    答:将公共 so 封装成 AAR,上传至 Maven 仓库;各业务模块通过 implementation 依赖,运行时统一使用 Relinker 加载,确保同一磁盘路径只存在一份 so,既节省体积又避免符号冲突。