实战项目一:构建一个简单的车载空调控制应用
说实话,车载空调控制这个场景,是我在接触 Android Automotive OS 时第一个想到的实战项目。为什么?因为它太典型了。你想想看,空调控制涉及温度、风量、模式这些物理参数,而这些参数恰恰是通过 CarPropertyService 与 VHAL 来管理的。说白了,这就是一个「应用层 ↔ 服务层 ↔ 硬件层」的完整链路。
我个人习惯,每学一个新系统,先拿一个具体的硬件控制项目练手。今天我们就来做一个车载空调控制应用,麻雀虽小,五脏俱全。
项目背景与目标
我们要实现的功能很简单:
- 显示当前车内温度
- 显示当前设定的目标温度
- 提供温度加减按钮
- 提供风量调节按钮
- 显示空调开关状态
嗯,听起来像是一个普通的 App?但关键在于,这些数据不是从本地数据库读的,而是从汽车的 VHAL(Vehicle Hardware Abstraction Layer)获取的。我们通过 CarPropertyService 来读写这些属性。
整体架构图
先来看一下我们这个项目的整体架构。我画了一张图,帮你理清各个模块之间的关系:
第一步:获取 Car 实例
在 Android Automotive OS 中,所有车辆相关的服务都通过 Car 对象来访问。你需要先获取 Car 实例,然后才能拿到 CarPropertyService。
// 在 Activity 或 Fragment 中
private Car car;
private CarPropertyService propertyService;
private void initCar() {
car = Car.createCar(this, new Car.CarServiceLifecycleListener() {
@Override
public void onConnected(Car car) {
// 连接成功,获取 CarPropertyService
propertyService = (CarPropertyService) car.getCarManager(
android.car.Car.PROPERTY_SERVICE
);
if (propertyService != null) {
Log.d(TAG, "CarPropertyService 连接成功");
loadCurrentAcState();
}
}
@Override
public void onDisconnected(Car car) {
propertyService = null;
Log.w(TAG, "Car 服务断开连接");
}
});
car.connect();
}
android.car.permission.CAR_PROPERTY_SERVICE 权限。记得在 AndroidManifest.xml 中添加这个权限,否则 onConnected 永远不会回调。
第二步:读取空调属性
空调相关的属性定义在 android.car.VehiclePropertyIds 中。常用的有:
| 属性 ID | 说明 | 数据类型 |
|---|---|---|
HVAC_TEMPERATURE_CURRENT |
当前车内温度 | float(摄氏度) |
HVAC_TEMPERATURE_SET |
目标设定温度 | float(摄氏度) |
HVAC_FAN_SPEED |
风扇转速 | int(0-100) |
HVAC_POWER_ON |
空调开关 | boolean |
读取当前温度的代码:
private float getCurrentTemperature() {
if (propertyService == null) return -1f;
try {
// 注意:需要指定区域 ID,通常驾驶位是 0
CarPropertyValue<Float> value = propertyService.getProperty(
VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT,
0 // 区域 ID,0 表示驾驶位
);
return value != null ? value.getValue() : -1f;
} catch (CarPropertyNotAvailableException e) {
Log.e(TAG, "读取温度失败", e);
return -1f;
}
}
CarPropertyNotAvailableException。在车辆启动初期,某些属性可能暂时不可用。如果你不捕获这个异常,应用会直接崩溃。所以,所有 getProperty 调用都要包在 try-catch 里。
第三步:设置空调属性
设置属性比读取稍微复杂一点。你需要构造一个 CarPropertyValue 对象,然后调用 setProperty。
private void setTargetTemperature(float temp) {
if (propertyService == null) return;
try {
// 构造属性值对象
CarPropertyValue<Float> value = new CarPropertyValue<>(
VehiclePropertyIds.HVAC_TEMPERATURE_SET,
0, // 区域 ID
temp // 目标温度
);
propertyService.setProperty(value);
Log.d(TAG, "设置目标温度: " + temp);
} catch (CarPropertyNotAvailableException e) {
Log.e(TAG, "设置温度失败", e);
}
}
你可能会问:为什么需要区域 ID?嗯,这是因为汽车空调通常支持分区控制——驾驶位和副驾驶位可以设置不同的温度。区域 ID 0 表示驾驶位,1 表示副驾驶位。
第四步:监听属性变化
光能读写还不够,我们还需要实时监听空调状态的变化。比如用户按了物理按键,或者系统自动调节了温度,我们的应用要能感知到。
private CarPropertyService.CarPropertyEventCallback callback;
private void registerCallback() {
if (propertyService == null) return;
callback = new CarPropertyService.CarPropertyEventCallback() {
@Override
public void onChangeEvent(CarPropertyValue value) {
// 属性值发生变化时回调
int propertyId = value.getPropertyId();
switch (propertyId) {
case VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT:
float currentTemp = (float) value.getValue();
updateCurrentTempDisplay(currentTemp);
break;
case VehiclePropertyIds.HVAC_TEMPERATURE_SET:
float setTemp = (float) value.getValue();
updateSetTempDisplay(setTemp);
break;
case VehiclePropertyIds.HVAC_POWER_ON:
boolean isOn = (boolean) value.getValue();
updatePowerStatus(isOn);
break;
}
}
@Override
public void onErrorEvent(int propertyId, int zone) {
Log.w(TAG, "属性监听出错: " + propertyId);
}
};
// 注册监听,需要指定要监听的属性列表
List<Integer> propertyIds = Arrays.asList(
VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT,
VehiclePropertyIds.HVAC_TEMPERATURE_SET,
VehiclePropertyIds.HVAC_FAN_SPEED,
VehiclePropertyIds.HVAC_POWER_ON
);
propertyService.registerCallback(callback, propertyIds);
}
propertyService.unregisterCallback(callback)。
第五步:完整的 UI 交互
最后,我们把所有功能串起来,做一个简单的 UI。布局文件就不贴了,直接看核心逻辑:
public class AcControlActivity extends AppCompatActivity {
private TextView tvCurrentTemp, tvSetTemp, tvFanSpeed;
private Button btnTempUp, btnTempDown, btnFanUp, btnFanDown;
private Switch switchPower;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ac_control);
initViews();
initCar();
setupListeners();
}
private void setupListeners() {
btnTempUp.setOnClickListener(v -> {
float current = getSetTemperature();
setTargetTemperature(current + 0.5f);
});
btnTempDown.setOnClickListener(v -> {
float current = getSetTemperature();
setTargetTemperature(current - 0.5f);
});
switchPower.setOnCheckedChangeListener((buttonView, isChecked) -> {
setAcPower(isChecked);
});
}
private void loadCurrentAcState() {
// 读取所有空调状态并更新 UI
float currentTemp = getCurrentTemperature();
float setTemp = getSetTemperature();
int fanSpeed = getFanSpeed();
boolean isOn = getAcPower();
runOnUiThread(() -> {
tvCurrentTemp.setText(String.format("%.1f°C", currentTemp));
tvSetTemp.setText(String.format("%.1f°C", setTemp));
tvFanSpeed.setText(fanSpeed + "%");
switchPower.setChecked(isOn);
});
}
}
小结
这个实战项目虽然简单,但覆盖了 CarPropertyService 的核心用法:连接服务、读写属性、监听变化。你想想看,掌握了这些,任何车载硬件控制应用你都能做——座椅调节、车窗控制、氛围灯,原理都是一样的。
我个人觉得,做车载开发最爽的一点就是:你写的代码能直接控制物理世界。按下按钮,空调真的吹出冷风,这种即时反馈是普通 App 开发给不了的。
- 通过 Car.createCar() 获取 Car 实例,再获取 CarPropertyService
- 使用 getProperty() 读取属性,setProperty() 写入属性
- 每个属性都需要指定区域 ID(zone ID)
- 注册 CarPropertyEventCallback 监听属性变化
- 别忘了处理 CarPropertyNotAvailableException
- 生命周期管理:及时注销回调,避免内存泄漏
嗯,这个项目就到这里。代码量不大,但每一行都有它的分量。如果你在实际开发中遇到问题,欢迎随时交流。
公众号:蓝海资料掘金营,微信deep3321