26、车载Launcher开发:Launcher架构、应用列表、快捷方式、壁纸与主题

车载Launcher,说白了就是车机系统的“门面”。用户一上车,第一眼看到的就是它。我参与过好几个车厂的项目,每次Launcher都是改动最频繁、争议最大的模块。为什么?因为它直接决定了用户对这台车的第一印象。

今天我们就来拆解车载Launcher的核心模块:架构怎么搭、应用列表怎么管理、快捷方式怎么实现、壁纸和主题系统怎么设计。嗯,内容不少,我们一个一个来。

26.1 Launcher的整体架构

车载Launcher和手机Launcher最大的区别是什么?手机Launcher是“应用启动器”,车载Launcher是“信息聚合中心”。你想想看,用户在车里不会频繁切应用,他需要的是——导航、音乐、空调、车辆状态这些信息,一眼就能看到。

我个人习惯把车载Launcher分成三层:

  • 数据层:负责管理应用列表、快捷方式、壁纸配置、主题资源。数据来源可能是本地数据库、ContentProvider、或者云端同步。
  • 业务层:处理用户交互逻辑,比如点击图标启动应用、长按拖拽排序、手势切换壁纸等。
  • UI层:渲染界面,包括主屏幕、应用抽屉、卡片组件、状态栏等。

这里我画了一张架构图,帮你理清关系:

车载Launcher三层架构 UI层 主屏幕 | 应用抽屉 | 卡片组件 | 状态栏 | 壁纸渲染 基于Fragment + RecyclerView + CarUiKit 业务层 点击启动 | 长按拖拽 | 手势切换 | 快捷方式管理 基于ViewModel + LiveData / Flow 数据层 应用列表 | 快捷方式 | 壁纸配置 | 主题资源 基于Room / ContentProvider / SharedPreferences

为什么要分层?我在项目中遇到过一个问题:一开始把数据查询和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") ?: "其他" }
小技巧: 我习惯在应用的 AndroidManifest.xml 里加一个 car_category 元数据,这样Launcher可以按“导航”、“音乐”、“电话”等类别展示,用户体验好很多。

还有一个坑:有些车机支持多用户(比如驾驶员和乘客模式),不同用户的应用列表不一样。我曾经踩过这个坑——切换用户后Launcher没有刷新列表,导致显示的还是上一个用户的应用。解决方案是监听 LauncherApps.OnAppsChangedCallback 回调,及时刷新。

26.3 快捷方式的实现

快捷方式(Shortcut)是车载Launcher的亮点。用户可以把常用功能(比如“导航回家”、“播放收藏歌单”)直接放在主屏幕,省去打开应用再操作的步骤。

Android 提供了 ShortcutManager 来管理快捷方式。但在车载场景下,我推荐用 CarShortcutManager(如果车厂有定制的话),或者自己维护一个快捷方式数据库。

实现思路是这样的:

  1. 应用通过 ShortcutManager.requestPinShortcut() 请求创建快捷方式。
  2. Launcher 接收 Intent,把快捷方式信息存入本地数据库。
  3. 主屏幕从数据库读取快捷方式列表,渲染成图标。
  4. 点击快捷方式时,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)
注意: 快捷方式的图标尺寸有讲究。车载屏幕分辨率差异很大,从 720p 到 4K 都有。我建议准备多套尺寸的图标,或者用矢量图(VectorDrawable),避免图标模糊。

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看起来简单,但要做好真的不容易。尤其是要适配不同车厂的定制需求,每个项目都有新坑。不过掌握了今天讲的这些基础架构和实现思路,至少能保证你拿到一个项目时,心里有底。

公众号:蓝海资料掘金营,微信deep3321