如何通过 sharedUserId 实现多个应用共享同一 UID?存在哪些风险?
解读
面试官想确认两点:
- 你是否真的动手配过 sharedUserId,还是只背过概念;
- 你对 Android 多应用隔离模型、签名机制、权限边界、升级兼容性的理解深度。
国内场景下,很多中小厂为了“插件化”“用户体系打通”会尝试 sharedUserId,但真正上线后被“升级失败”“权限爆炸”“安全合规审计”打回的案例极多,所以面试官希望听到“落地经验 + 风险清单 + 替代方案”。
知识点
- 应用 UID 与进程沙箱:安装时 PMS 通过 PackageParser 解析 APK,未声明 sharedUserId 则分配唯一 UID;若声明且签名一致,则复用已有 UID。
- 签名一致性校验:系统只比对「签名证书指纹」,不比对 v1/v2/v3 方案细节;国内厂商某些魔改 ROM 会额外校验签名算法 OID,导致同证书仍失败。
- 权限继承:sharedUserId 应用获得同一 GID、同一 SELinux 域,可相互访问私有数据目录 /data/data/<pkg>/、共享 SharedPreference、数据库、甚至 native 代码段。
- 升级陷阱:已安装 APK 无 sharedUserId,后续版本追加,系统拒绝安装(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE);反之去掉 sharedUserId 也会失败。国内渠道分发时,若用户通过应用商店增量更新,商店往往直接拉取最新 APK 做全量替换,失败率更高。
- 国内合规:工信部 164 号文、26 号通报把「超范围收集」列为重点;sharedUserId 共享目录后,若 A 应用把用户数据写入公共区,被 B 应用读取,极易被检测为「未明示共享」而通报下架。
- 替代技术:ContentProvider+signature 级自定义权限、AIDL+in-app Service、Broadcast+signature 权限、FileProvider+FLAG_GRANT_READ_URI_PERMISSION、Binder RPC 封装 SDK、动态交付(Dynamic Feature)等。
答案
步骤:
- 在所有需要共享 UID 的 APK 的 AndroidManifest 根节点添加同一属性:
android:sharedUserId="com.example.foo"
字符串值任意,但必须在同一设备全局唯一,建议用公司反域名。 - 使用完全相同签名证书对 APK 进行签名;v1+v2 同时打开,避免部分 ROM 只验 v1。
- 首次安装时,系统 PMS 在 Settings#mSharedUsers 中记录 sharedUserId→UID 映射;后续 APK 只要签名一致即复用该 UID。
- 代码层即可通过 createPackageContext("sister.pkg", CONTEXT_IGNORE_SECURITY) 获取对方 Context,直接 openFileInput、getDatabasePath、甚至 ClassLoader 加载代码。
风险清单(面试时逐条展开,体现踩坑经验):
- 升级断裂:已发布版本未带 sharedUserId,后续无法追加;只能换新包名或强制清数据,导致用户流失。
- 权限膨胀:A 应用申请 READ_SMS,B 应用自动获得,隐私合规审计无法解释。
- 数据污染:共享目录下文件命名冲突、数据库版本升降级不一致,极易出现 Crash 或数据损坏。
- 安全放大:若其中一款应用被植入漏洞,攻击者可直接读写姐妹应用私有数据,横向扩展攻击面;国内 SRC 审核时会被认定为「高风险横向移动」。
- 系统限制:Android 10 以后共享库路径变化、Android 13 开始 SELinux 域进一步细化,部分 ROM 已出现 sharedUserId 应用无法访问 /data/app/…/lib 的 case。
- 未来废弃:Google 在 AOSP 13 标记 sharedUserId 为 deprecated,未来可能彻底移除;国内头部厂商已跟进提示「不建议使用」。
因此,线上项目若无历史包袱,应优先采用 signature 级自定义权限 + ContentProvider 方案;若已使用 sharedUserId,需提前规划「包名双开 + 数据迁移脚本」平滑退场。
拓展思考
- 如果三方 SDK 也用了 sharedUserId,如何检测冲突?
答:在 CI 阶段写 Gradle 脚本解析 merged_manifest,扫描所有 sharedUserId 值,出现不一致即中断打包;同时解析签名证书,确保所有依赖模块使用同一证书。 - 国内某些运营商 ROM 对 sharedUserId 应用做「冻结」处理,导致无法后台拉起,如何兼容?
答:在 AndroidManifest 中声明 android:sharedUserMaxSdkVersion="29" 让 30 以上设备不再共享,同时用 WorkManager+BatteryHistorian 验证后台存活率,低于阈值则提示用户手动关闭省电限制。 - 若必须共享 native 层 so,而 sharedUserId 被禁用,有何替代?
答:将公共 so 封装成 AAR,上传至 Maven 仓库;各业务模块通过 implementation 依赖,运行时统一使用 Relinker 加载,确保同一磁盘路径只存在一份 so,既节省体积又避免符号冲突。