26、车载Launcher开发:Launcher架构、应用列表、快捷方式、壁纸与主题
车载Launcher,说白了就是车机系统的“门面”。用户一上车,第一眼看到的就是它。我参与过好几个车厂的项目,每次Launcher都是改动最频繁、争议最大的模块。为什么?因为它直接决定了用户对这台车的第一印象。
今天我们就来拆解车载Launcher的核心模块:架构怎么搭、应用列表怎么管理、快捷方式怎么实现、壁纸和主题系统怎么设计。嗯,内容不少,我们一个一个来。
26.1 Launcher的整体架构
车载Launcher和手机Launcher最大的区别是什么?手机Launcher是“应用启动器”,车载Launcher是“信息聚合中心”。你想想看,用户在车里不会频繁切应用,他需要的是——导航、音乐、空调、车辆状态这些信息,一眼就能看到。
我个人习惯把车载Launcher分成三层:
- 数据层:负责管理应用列表、快捷方式、壁纸配置、主题资源。数据来源可能是本地数据库、ContentProvider、或者云端同步。
- 业务层:处理用户交互逻辑,比如点击图标启动应用、长按拖拽排序、手势切换壁纸等。
- UI层:渲染界面,包括主屏幕、应用抽屉、卡片组件、状态栏等。
这里我画了一张架构图,帮你理清关系:
为什么要分层?我在项目中遇到过一个问题:一开始把数据查询和UI渲染混在一起,结果每次更新应用列表,界面都要卡顿几百毫秒。分层之后,数据层用协程异步加载,UI层只负责展示,问题就解决了。
26.2 应用列表的管理
车载应用列表和手机不一样。手机恨不得把所有应用都展示出来,车载只展示“适合开车时用的应用”。比如游戏、视频类应用,在行驶中应该被隐藏。
我建议用 LauncherApps API 来获取已安装应用列表,然后根据车辆状态做过滤:
// 获取所有已安装应用
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val mainUser = Process.myUserHandle()
val allApps = launcherApps.getActivityList(null, mainUser)
// 过滤:只保留车载兼容的应用
val carApps = allApps.filter { activityInfo ->
activityInfo.metaData?.getBoolean("com.android.car.requires", false) == true
}
// 按类别分组
val groupedApps = carApps.groupBy { it.metaData?.getString("car_category") ?: "其他" }
car_category 元数据,这样Launcher可以按“导航”、“音乐”、“电话”等类别展示,用户体验好很多。
还有一个坑:有些车机支持多用户(比如驾驶员和乘客模式),不同用户的应用列表不一样。我曾经踩过这个坑——切换用户后Launcher没有刷新列表,导致显示的还是上一个用户的应用。解决方案是监听 LauncherApps.OnAppsChangedCallback 回调,及时刷新。
26.3 快捷方式的实现
快捷方式(Shortcut)是车载Launcher的亮点。用户可以把常用功能(比如“导航回家”、“播放收藏歌单”)直接放在主屏幕,省去打开应用再操作的步骤。
Android 提供了 ShortcutManager 来管理快捷方式。但在车载场景下,我推荐用 CarShortcutManager(如果车厂有定制的话),或者自己维护一个快捷方式数据库。
实现思路是这样的:
- 应用通过
ShortcutManager.requestPinShortcut()请求创建快捷方式。 - Launcher 接收 Intent,把快捷方式信息存入本地数据库。
- 主屏幕从数据库读取快捷方式列表,渲染成图标。
- 点击快捷方式时,Launcher 通过 Intent 启动对应的应用和操作。
// 创建快捷方式
val shortcut = ShortcutInfo.Builder(context, "nav_home")
.setShortLabel("导航回家")
.setLongLabel("导航到家庭地址")
.setIcon(Icon.createWithResource(context, R.drawable.ic_home))
.setIntent(Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("geo:0,0?q=home")
setPackage("com.example.nav")
})
.build()
// 请求固定到Launcher
shortcutManager.requestPinShortcut(shortcut, null)
26.4 壁纸与主题系统
壁纸和主题,是车载Launcher的“颜值担当”。但这里有个矛盾:用户想要个性化,车厂想要品牌统一。怎么平衡?
我参与的一个项目是这样设计的:
- 壁纸:支持静态壁纸和动态壁纸。静态壁纸用
WallpaperManager设置,动态壁纸用WallpaperService。但动态壁纸在车载上要慎用——耗电、占内存,还可能影响系统流畅度。 - 主题:通过
ThemeManager(如果有的话)或者自定义的Configuration切换。主题包括颜色、字体、图标风格、圆角大小等。
这里我画了一个主题切换的流程图:
具体实现时,我推荐用 ThemeOverlay 的方式。什么意思呢?就是主题不改变底层资源,而是叠加一层样式。比如:
// 应用主题覆盖
val themeOverlay = Resources.Theme()
themeOverlay.applyStyle(R.style.Theme_Car_Dark, true)
context.theme.setTo(themeOverlay)
// 所有使用 ?attr/ 引用的颜色会自动更新
// 比如:android:background="?attr/carPrimaryColor"
这样做的好处是切换主题时不用重建Activity,只需要调用 recreate() 或者手动刷新View即可。我在项目中用这种方式,主题切换速度从原来的 800ms 降到了 200ms 以内。
26.5 避坑指南
最后分享几个我踩过的坑:
- 应用列表刷新时机:不要每次安装/卸载应用都全量刷新列表。我建议用 DiffUtil 做增量更新,只更新变化的部分。
- 壁纸内存占用:车载屏幕分辨率高,一张壁纸可能 4K 甚至 8K。加载时一定要做采样压缩,否则 OOM 是迟早的事。
- 主题与夜间模式:很多车机有自动夜间模式(根据时间或光线传感器)。主题系统要和夜间模式联动,否则会出现白天用暗色主题、晚上用亮色主题的尴尬情况。
- 快捷方式持久化:用户重启车机后,快捷方式不能丢。我建议用 Room 数据库存储,而不是 SharedPreferences,因为数据量大时 SharedPreferences 性能堪忧。
核心要点回顾:
- 车载Launcher是信息聚合中心,不是应用启动器
- 应用列表要按车辆状态和类别过滤
- 快捷方式用 ShortcutManager + 本地数据库实现
- 壁纸和主题用 ThemeOverlay 方式实现快速切换
- 注意内存、性能、多用户兼容性
好了,车载Launcher开发的核心内容就这些。说实话,Launcher看起来简单,但要做好真的不容易。尤其是要适配不同车厂的定制需求,每个项目都有新坑。不过掌握了今天讲的这些基础架构和实现思路,至少能保证你拿到一个项目时,心里有底。