车载Android应用-多用户场景下的单例模式

1. 背景

由于车机相对于移动设备的硬件配置变化:单个SOC+单屏幕/多个小屏幕 -> 单个SOC+多个大屏幕,同时响应多个用户的交互在车机的场景下变成一个必需特性。

Android内置了多用户的支持,且经过多个版本的迭代后逐渐完善。

下图是Android 14多屏幕 车机 模拟器 的截图(左起依次是主屏、仪表屏、副屏1、副屏2):

默认情况下,同一个包在不同的用户下是各自独立的进程,每个用户的数据也是独立存储的。

但在某些业务场景下,例如响应 "调整空调温度" ,"音量调整"等对车辆硬件进行控制的指令。应用需要在一个位置保存所有用户产生的数据,处理所有用户发出的指令或消息。

多用户下单例运行就可以满足这些需求,在Android中内置了singleUser组件用于此场景。

Service,ContentProvider和BroadcastReceiver都支持单例模式,单例模式的进程运行于用户0(系统用户)中。

Activity目前不支持单例模式,推测将来也不会支持,因为从交互设计来考虑,同一时间多个用户的UI应该各自独立。

相对于一般模式,只需要少量的修改就可以实现单例模式。

2. 实现

2.1. 创建单例组件

  • 按正常方式实现 Service/ContentProvider/BroadcastReceiver
  • 在AndroidManifest.xml中给对应组件添加singleUser属性,设置为true
  • 给应用添加使用权限 "android.permission.INTERACT_ACROSS_USERS_FULL"
  • 应用使用平台签名
  • 设置为特权privileged 应用
    • 应用需要集成到system/vendor等分区的priv-app目录下
    • 为应用设置权限白名单privapp-permission,可参考system/etc/permissions/com.android.car.settings.xml

2.2. 与单例组件交互

与Service/ContentProvider/BroadcastReceiver单例进行交互的代码,不需要进行额外修改

  • Service: startService/bindService[#Context]
  • ContentProvider: query/insert/update/delete/registerContentObserver/unregisterContentObserver[#ContentResolver]
  • BroadcastReceiver: sendBroadcast[#Context]

3. Showcase

基于AOSP 14 sdk_car_md_x86_64模拟器

源码: A demo for platform single user applications

3.1. Service(AIDL)

  • 实现一个单例AIDL Service(SingleUserService)
  • 添加一个MainActivity(如第三节首图),通过bindService与SingleUserService通信

观察Logcat中的进程ID,可以看到服务运行在user 0,Activity运行在user 12

3.2. ContentProvider

  • 实现一个单例ContentProvider(SingleUserProvider)
  • 添加一个MainActivity(如第三节首图),通过ContentResolver读写SingleUserProvider

观察Logcat中的进程ID,可以看到Provider运行在user 0,Activity运行在user 12

3.3. BroadcastReceiver

  • 实现一个单例BroadcastReceiver(SingleUserReceiver)
  • 添加一个MainActivity(如第三节首图),通过sendBroadcast发送广播给SingleUserReceiver

观察Logcat中的进程ID,可以看到Receiver运行在user 0,Activity运行在user 12

4. 其他

  • 只有特权应用可以支持单例模式,所以第三方应用只能通过其他方式实现类似效果
  • 支持单例的应用可以禁用掉非系统用户下的单例组件以节省资源,如:
scss 复制代码
// Add on any entry points of the host applications
if (!UserManager.isSystemUser()) {
    ComponentName targetComponent = new ComponentName(this, TargetService.class);
    context.getPackageManager().setComponentEnabledSetting(targetComponent, COMPONENT_ENABLED_STATE_DISABLED, 0);
}

参考资料:

source.android.google.​cn/devices/tec... source.android.google.​cn/docs/device...

相关推荐
“码”力全开3 小时前
解耦异构算力与多协议接入:基于Docker与源码交付的开源企业级GB28181/RTSP边缘计算AI视频管理平台架构深度解析
人工智能·docker·开源
小北的AI科技分享3 小时前
指尖上的工业革命:Open Claw如何重塑现代抓取技术
开源·抓取·
冬奇Lab4 小时前
每日一个开源项目(第117篇):Recordly - 零剪辑基础也能制作电影级产品演示视频
开源·资讯
FIT2CLOUD飞致云6 小时前
支持AI网关和Skills Hub,1Panel企业版正式发布
ai·开源·1panel
lzguornzhang6 小时前
VMware Workstation Pro 25H2 安装 openEuler 24.03 LTS SP3 操作系统完整教程
开源
cup117 小时前
[开源] 全屏时钟 / Full Clock:放弃 time.is,用 Svelte 5 写了一个极致纯净的全屏时钟,解决秒数焦虑
开源·工具·时钟·效率·svelte
java_cj8 小时前
数据库范式化设计与性能优化全攻略
数据库·后端·性能优化·架构·开源
Lethehong8 小时前
拒绝吃灰!手把手教你把“全能AI助理”无缝塞进微信/QQ,打造属于你的数字分身
人工智能·开源·蓝耘元生代·蓝耘maas·qwenpaw
kTR2hD1qb9 小时前
近期使用Claude Code + Opus4.7设计开发了一个开源项目:Qianyuan AI Agentic Framework
人工智能·开源
妄想出头的工业炼药师9 小时前
激光雷达点云退化多地图会话
算法·开源