11、车载应用开发基础:Automotive应用架构与第一个车载应用
各位同学,欢迎来到第11章。从这一章开始,我们真正进入车载应用开发的核心地带。说实话,车载应用和手机应用虽然都叫“App”,但背后的设计思路、API体系、甚至UI规范,完全是两码事。我当年从手机端转到车载开发时,第一个感觉就是——这玩意儿怎么这么多规矩?
但等你理解了它的设计哲学,你会发现,这些“规矩”其实都是为了安全、稳定和高效。今天我们就从架构、API、UI规范,再到一个完整的车速显示应用,一步步走通。
11.1 Automotive应用架构:CarApp与CarActivity
先问大家一个问题:车载应用为什么不能直接用普通的Activity?
原因很简单——车载系统需要管理多窗口、多用户、驾驶安全优先级。普通的Activity生命周期在车载场景下根本不够用。Google为此专门设计了CarApp和CarActivity这套架构。
11.1.1 CarApp——应用的“总管”
CarApp是一个抽象类,它管理着整个车载应用的生命周期。我个人习惯把它理解成“应用级别的管家”。它负责的事情包括:
- 管理
CarActivity的创建和销毁 - 处理车载特有的生命周期事件(比如驾驶模式切换)
- 提供
CarContext,这是车载应用的上下文,比普通Context多了很多车载API
来看一个最简单的CarApp实现:
public class MyCarApp extends CarApp {
@Override
public void onCreate() {
super.onCreate();
// 初始化车载服务连接
// 我在项目中习惯在这里做一次全局的Car连接检查
}
@Override
public CarActivity createCarActivity(Class<? extends CarActivity> activityClass) {
// 返回对应的CarActivity实例
return new MyCarActivity();
}
}
注意,CarApp必须在AndroidManifest中声明,并且使用android:name=".MyCarApp"。这个坑我踩过——忘了声明,结果应用启动直接崩溃。
11.1.2 CarActivity——车载版的Activity
CarActivity是车载应用中的基本界面单元。它和普通Activity最大的区别在于:
- 生命周期受
CarApp控制,而不是系统直接管理 - 自带
CarContext,可以直接调用车载API - 支持多窗口和分屏模式
我曾经在项目中遇到一个问题:普通Activity在车载系统上无法正确响应驾驶模式切换。后来换成CarActivity,一切正常。说白了,这就是“专业的事交给专业的类”。
public class MyCarActivity extends CarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置布局
setContentView(R.layout.activity_main);
}
@Override
public void onStart() {
super.onStart();
// 车载特有的启动逻辑
}
}
11.2 Car API使用:CarConnection与CarOccupantZone
车载应用离不开车辆数据。怎么拿到车速、车门状态、空调信息?这就需要用到Car API。这里我重点讲两个最常用的:CarConnection和CarOccupantZone。
11.2.1 CarConnection——连接车辆服务的桥梁
CarConnection负责管理应用与车辆服务之间的连接。你想想看,车辆数据是实时变化的,如果连接不稳定,车速显示就会卡顿甚至出错。
使用步骤很简单:
- 通过
Car.createCar()获取Car实例 - 调用
car.connect()建立连接 - 通过
CarConnection监听连接状态
Car car = Car.createCar(context, new Car.CarServiceLifecycleListener() {
@Override
public void onConnected(Car car) {
// 连接成功,可以获取车辆数据了
Log.d("CarApp", "车辆服务已连接");
}
@Override
public void onDisconnected(Car car) {
// 连接断开,需要处理重连逻辑
Log.w("CarApp", "车辆服务断开");
}
});
car.connect();
嗯,这里要注意:connect()是异步操作,不要在UI线程中等待结果。我见过有同事直接在onCreate里写死循环等连接,结果ANR了。
11.2.2 CarOccupantZone——区分乘客区域
车载系统里,不同座位的人看到的内容可能不同。比如驾驶员不能看视频,但副驾可以。CarOccupantZone就是用来标识“这个界面属于哪个座位”。
它的典型用法:
CarOccupantZoneManager zoneManager =
(CarOccupantZoneManager) car.getCarManager(Car.OCCUPANT_ZONE_SERVICE);
// 获取驾驶员区域
CarOccupantZone driverZone = zoneManager.getDriverOccupantZone();
// 获取副驾区域
CarOccupantZone passengerZone = zoneManager.getPassengerOccupantZone();
// 判断当前应用是否在驾驶员区域
if (zoneManager.isDriverOccupantZone(driverZone)) {
// 限制某些操作,比如禁止播放视频
}
我在做导航应用时,就用这个API实现了“驾驶员模式”——当检测到当前是驾驶员区域时,自动隐藏视频播放按钮。这个功能在车厂验收时是必查项,大家一定要重视。
11.3 车载UI设计规范:安全驾驶与大屏适配
车载UI设计和手机UI设计完全是两码事。手机可以追求炫酷的动画、复杂的交互,但车载UI的第一原则是——安全。
11.3.1 安全驾驶原则
我总结了几条必须遵守的规则:
- 驾驶时禁止复杂操作:任何需要盯着屏幕超过2秒的操作,都不应该在驾驶时出现
- 文字大小有底线:最小字号建议不低于16sp,关键信息(如车速)不低于24sp
- 触控区域要够大:按钮最小尺寸建议48dp×48dp,最好64dp以上
- 颜色对比要清晰:不要用低对比度的配色,阳光直射下根本看不清
我曾经在一个项目中,把设置按钮做成了32dp大小,结果车厂测试时直接打回——驾驶员在行驶中根本点不准。后来改成了56dp,一次通过。
11.3.2 大屏适配技巧
现在的车载屏幕越来越大,12.3英寸、15.6英寸都很常见。大屏适配有几个要点:
- 不要简单拉伸:手机布局直接放大,会显得很空旷
- 善用网格系统:推荐使用8dp或4dp的网格,让元素对齐更规整
- 信息分层展示:重要信息(车速、导航)放中间,次要信息(音乐、设置)放两侧
- 支持多窗口:你的应用可能和导航、音乐同时显示,要能自适应
我一般会先设计一个“最小安全区域”——就是驾驶员视线最自然的区域,大概在屏幕中央偏左的位置。所有关键信息都放在这个区域内。次要信息放在两侧,但保证驾驶员不需要转头就能扫到。
11.4 第一个车载应用:车速显示
理论讲完了,我们来动手写一个真正的车载应用——车速显示。这个应用虽然简单,但涵盖了CarApp、CarActivity、Car API的完整使用流程。
11.4.1 项目结构
先看整体结构:
app/
├── src/main/
│ ├── java/com/example/speeddisplay/
│ │ ├── SpeedCarApp.java // CarApp实现
│ │ ├── SpeedCarActivity.java // CarActivity实现
│ │ └── SpeedViewModel.java // 车速数据管理
│ ├── res/
│ │ ├── layout/
│ │ │ └── activity_speed.xml // 布局文件
│ │ └── values/
│ │ └── strings.xml
│ └── AndroidManifest.xml
└── build.gradle
11.4.2 核心代码实现
第一步:CarApp
public class SpeedCarApp extends CarApp {
@Override
public CarActivity createCarActivity(Class<? extends CarActivity> activityClass) {
return new SpeedCarActivity();
}
}
第二步:CarActivity + 车速获取
public class SpeedCarActivity extends CarActivity {
private Car car;
private VehicleHal vehicleHal;
private TextView speedText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_speed);
speedText = findViewById(R.id.speed_text);
// 连接车辆服务
car = Car.createCar(this, new Car.CarServiceLifecycleListener() {
@Override
public void onConnected(Car car) {
// 获取VehicleHal服务
vehicleHal = (VehicleHal) car.getCarManager(VehicleHal.class);
// 订阅车速数据
subscribeSpeed();
}
@Override
public void onDisconnected(Car car) {
// 清理资源
}
});
car.connect();
}
private void subscribeSpeed() {
// 订阅车辆速度属性
vehicleHal.subscribeProperty(
VehiclePropertyIds.PERF_VEHICLE_SPEED,
new VehicleHalCallback() {
@Override
public void onPropertyEvent(VehiclePropValue value) {
float speed = value.getValue().getFloatValue();
// 更新UI(注意要在主线程)
runOnUiThread(() -> {
speedText.setText(String.format("%.0f km/h", speed));
});
}
}
);
}
@Override
public void onDestroy() {
super.onDestroy();
if (car != null) {
car.disconnect();
}
}
}
第三步:布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="#1A1A2E">
<TextView
android:id="@+id/speed_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-- km/h"
android:textSize="72sp"
android:textColor="#00D4AA"
android:fontFamily="sans-serif-medium" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前车速"
android:textSize="18sp"
android:textColor="#888888"
android:layout_marginTop="8dp" />
</LinearLayout>
11.4.3 运行效果
这个应用启动后,会显示一个简洁的车速界面。数字很大,颜色醒目,驾驶员一眼就能看到。嗯,这里要注意:实际部署时,车速数据来源可能不同,有的车厂用PERF_VEHICLE_SPEED,有的用VEHICLE_SPEED_DISPLAY,需要根据具体车型调整。
- CarApp管理应用生命周期,CarActivity管理界面
- CarConnection负责连接车辆服务,必须异步处理
- CarOccupantZone用于区分乘客区域,实现安全策略
- 车载UI以安全为第一原则,大屏适配要信息分层
- 车速显示应用是车载开发的“Hello World”,但麻雀虽小五脏俱全
11.5 本章知识体系图
下面这张图总结了本章的核心知识结构,大家可以对照着复习:
好了,这一章的内容就到这里。从架构到API,从规范到实战,我们一步步把车载应用开发的根基打牢了。下一章我们会深入车辆数据服务,看看怎么获取更多车辆信息,比如转速、油耗、车门状态等。到时候再给大家分享一些我在项目中踩过的坑。