15、Launcher 开发:车载 Launcher 架构、自定义 Launcher、快捷控制中心

车载 Launcher,说白了就是车机系统的「门面」。用户一上车,第一眼看到的就是它。我参与过好几个量产项目,每次 Launcher 的改动都是最频繁的,因为它是用户感知最强的模块。

今天咱们就聊聊车载 Launcher 的架构设计、如何自定义一个 Launcher,以及快捷控制中心(Quick Control Center)的实现思路。

15.1 车载 Launcher 的架构设计

Android Automotive OS 的 Launcher 和手机上的 Launcher 有本质区别。手机 Launcher 主要管应用图标和桌面壁纸,但车载 Launcher 要管的东西多得多——导航卡片、媒体控制、车辆状态、空调调节……

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

  • 底层(SystemUI 层):处理系统级 UI,比如状态栏、导航栏、锁屏。这部分和 SystemUI 进程绑定。
  • 中间层(Launcher 层):负责主界面布局、应用列表、快捷卡片。这是咱们今天重点讨论的。
  • 上层(应用层):第三方应用或 OEM 自研应用,通过 Launcher 提供的接口展示内容。

核心要点:车载 Launcher 本质上是一个「聚合器」,它把导航、媒体、车辆信息、应用入口整合到一个界面里。不要把它当成单纯的桌面启动器。

我记得在某个项目里,产品经理要求 Launcher 同时显示导航剩余里程、当前播放歌曲、空调温度、胎压监测……嗯,这已经不是 Launcher 了,这是仪表盘。

15.2 自定义 Launcher 的架构

自定义 Launcher 其实不复杂,但有几个坑要避开。我直接说做法。

15.2.1 基础搭建

首先,你得在 AndroidManifest 里声明 Launcher Activity:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"
    android:theme="@style/Theme.CarLauncher">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

注意 launchMode="singleTask",这个很重要。我曾经遇到过用户按 Home 键后 Launcher 被重复创建的问题,加上 singleTask 就解决了。

15.2.2 布局设计

车载屏幕通常是横屏,而且分辨率差异很大(从 720p 到 4K 都有)。我建议用 ConstraintLayout 做根布局,配合百分比约束。

一个典型的车载 Launcher 布局:

  • 顶部:状态栏(时间、信号、电量)
  • 中间:内容区(导航卡片、媒体卡片、快捷应用)
  • 底部:Dock 栏(常用应用、返回 Home 按钮)
  • 左侧或右侧:快捷控制中心入口

个人经验:不要把布局写死。用 CarUiRecyclerView 或者自定义 ViewGroup 来做动态布局,方便不同分辨率的适配。我在一个项目里吃过硬编码 dp 的亏,换了屏幕尺寸后整个布局都崩了。

15.2.3 应用列表管理

车载 Launcher 不需要显示所有应用。你想想看,谁会在一辆车上玩《原神》?所以我们要过滤应用。

我一般用 PackageManager 配合 Intent.ACTION_MAIN + Intent.CATEGORY_LAUNCHER 来获取应用列表,然后根据车辆场景做过滤:

fun getCarApps(packageManager: PackageManager): List<ResolveInfo> {
    val intent = Intent(Intent.ACTION_MAIN).apply {
        addCategory(Intent.CATEGORY_LAUNCHER)
    }
    val allApps = packageManager.queryIntentActivities(intent, 0)
    return allApps.filter { resolveInfo ->
        val packageName = resolveInfo.activityInfo.packageName
        // 过滤掉非车载应用
        !excludedPackages.contains(packageName)
    }
}

避坑指南:我曾经在过滤应用时忘了处理 android:enabled="false" 的情况,结果有些被禁用的应用还是显示出来了。记得检查 resolveInfo.activityInfo.enabled

15.3 快捷控制中心(Quick Control Center)

快捷控制中心是车载 Launcher 的「灵魂」。用户开车时不可能像玩手机一样点来点去,他们需要一键操作。

15.3.1 功能设计

一个标准的快捷控制中心应该包含:

功能模块 说明 实现方式
空调控制 温度、风量、模式 通过 CarService 的 HVAC 接口
媒体控制 播放/暂停、上一首/下一首 MediaSession 回调
导航快捷 回家、去公司、搜索 Intent 调起导航应用
车辆设置 驾驶模式、灯光、门窗 CarPropertyManager
快捷应用 最近使用、常用应用 PackageManager + 本地存储

15.3.2 实现思路

快捷控制中心我建议用 BottomSheet 或者侧滑面板实现。为什么?因为用户操作时不需要离开主界面,体验更连贯。

具体做法:

  1. 在 Launcher 布局中预留一个 Fragment 或 ViewStub
  2. 监听用户手势(从边缘滑动或点击按钮)
  3. 动态加载控制面板的内容
  4. 每个控制项绑定对应的 CarService API

举个空调控制的例子:

// 获取 HVAC 控制接口
val car = Car.createCar(context)
val hvacManager = car.getCarManager(Car.HVAC_SERVICE) as CarHvacManager

// 设置驾驶员侧温度
hvacManager.setIntProperty(
    CarHvacManager.ID_ZONED_TEMP_SETPOINT,
    VehicleAreaSeat.SEAT_ROW_1_LEFT,
    22  // 22 摄氏度
)

注意:CarService 的 API 在不同 Android 版本上有差异。Android 12 之后引入了 CarPropertyManager,建议用这个统一接口。我手头有个项目还在用 Android 11,两套 API 都得兼容,挺头疼的。

15.4 知识体系总览

下面这张图是我自己总结的 Launcher 开发知识体系,你可以对照着看:

车载 Launcher 开发知识体系 SystemUI 层 Launcher 层 应用层 布局管理 应用列表过滤 快捷控制中心 CarService 集成 空调控制 媒体控制 导航快捷 车辆设置 核心:Launcher 是聚合器,不是启动器 架构设计 → 布局实现 → 功能集成 → 快捷控制

15.5 一些实战建议

最后,分享几个我在项目中踩过的坑:

  • 性能问题:车载设备的硬件配置参差不齐。我遇到过用低端芯片跑 4K 屏幕的情况,Launcher 卡成 PPT。解决办法是减少过度绘制,能用 ViewStub 就别用 View.GONE
  • 多用户问题:Android Automotive 支持多用户(驾驶员、乘客)。Launcher 要区分当前用户,不同用户的快捷控制中心配置不一样。我一开始没注意这个,结果驾驶员 A 的设置被驾驶员 B 改了。
  • 安全限制:车辆行驶中,某些操作(比如视频播放、详细设置)要禁用。这个通过 CarOccupantZoneManager 判断当前座位和车辆状态来实现。

一个小技巧:调试 Launcher 时,用 adb shell am start -W -n com.example.carlauncher/.MainActivity 可以查看启动耗时。我一般要求 Launcher 冷启动不超过 500ms,热启动不超过 200ms。

好了,Launcher 开发的核心内容就这些。说白了,车载 Launcher 就是一个「聚合器 + 快捷操作面板」,把用户最需要的东西放在最顺手的位置。你想想看,开车的时候谁还有空去翻应用抽屉?

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