9、车载设置与车辆信息:CarPropertyManager读取车速/油量/空调、CarSensorManager、车辆设置面板开发

各位同学,今天我们来聊一个很实在的话题——车载应用怎么跟车辆本身“对话”。说白了,就是你的App怎么知道车现在跑多快、油箱还剩多少油、空调设了多少度。这些数据,在Android Automotive里主要通过CarPropertyManagerCarSensorManager来获取。

我在做第一个车载项目时,踩过一个坑:以为车速数据随时都能读到,结果发现有些属性需要特定权限,有些数据在车辆熄火后就拿不到了。嗯,今天咱们就把这些门道讲清楚。

9.1 CarPropertyManager:车辆属性的“万能钥匙”

CarPropertyManager是Android Automotive里最核心的车辆数据接口。它管理着车辆的各种属性——车速、油量、空调温度、车门状态等等。你可以把它理解成一个“属性仓库”,每个属性都有一个唯一的ID。

核心概念:每个车辆属性都有一个PropertyId,通过这个ID来读写数据。属性可以是整数、浮点数、字符串,甚至是复杂的对象。

9.1.1 获取CarPropertyManager实例

第一步,先拿到Car对象,然后获取管理器。代码很简单:

// 获取Car实例
Car car = Car.createCar(context);

// 获取CarPropertyManager
CarPropertyManager propertyManager = 
    (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);

// 别忘了在不用的时候释放
car.disconnect();

我个人习惯在onCreate里初始化,在onDestroy里释放。你想想看,如果忘记释放,会导致内存泄漏,这在车载系统上可是大忌。

9.1.2 读取车速

车速是最常用的车辆数据之一。它的PropertyId是VehiclePropertyIds.PERF_VEHICLE_SPEED。读取方式如下:

// 读取当前车速(单位:千米/小时)
float speed = propertyManager.getProperty(
    Float.class, 
    VehiclePropertyIds.PERF_VEHICLE_SPEED, 
    0  // areaId,一般传0
);

// 注册监听,实时获取车速变化
propertyManager.registerCallback(callback, 
    VehiclePropertyIds.PERF_VEHICLE_SPEED, 
    0);

小提示:车速数据是浮点数,单位是km/h。有些车型会返回m/s,记得做单位转换。我在项目中遇到过一台测试车,返回的速度值比实际快了一倍,后来发现是单位配置问题。

9.1.3 读取油量

油量信息稍微复杂一点。它有两个相关属性:FUEL_LEVEL(当前油量)和FUEL_LEVEL_LOW(低油量警告)。

// 读取当前油量(单位:升)
float fuelLevel = propertyManager.getProperty(
    Float.class, 
    VehiclePropertyIds.FUEL_LEVEL, 
    0
);

// 检查是否低油量
boolean isLowFuel = propertyManager.getProperty(
    Boolean.class, 
    VehiclePropertyIds.FUEL_LEVEL_LOW, 
    0
);

// 显示到UI上
textView.setText("剩余油量: " + fuelLevel + " L");

我曾经在测试时发现,有些车在油箱加满后,FUEL_LEVEL返回的值会超过标称容量。嗯,这其实是传感器的误差范围,建议你在UI上做个上限截断。

9.1.4 读取空调状态

空调控制涉及多个属性:目标温度、风量、模式等。这里以读取空调开关状态和目标温度为例:

// 读取空调开关状态
boolean acOn = propertyManager.getProperty(
    Boolean.class, 
    VehiclePropertyIds.HVAC_AC_ON, 
    VehicleArea.ZONE_ROW_1  // 第一排座位区域
);

// 读取目标温度(单位:摄氏度)
float targetTemp = propertyManager.getProperty(
    Float.class, 
    VehiclePropertyIds.HVAC_TEMPERATURE_SET, 
    VehicleArea.ZONE_ROW_1
);

// 设置空调温度
propertyManager.setProperty(
    Float.class, 
    VehiclePropertyIds.HVAC_TEMPERATURE_SET, 
    VehicleArea.ZONE_ROW_1, 
    24.5f  // 设置到24.5度
);

注意:空调属性通常需要CONTROL_CAR_CLIMATE权限。没有权限的话,setProperty会抛出SecurityException。记得在AndroidManifest里声明权限。

9.2 CarSensorManager:传感器数据的“专属通道”

有些车辆数据不适合通过CarPropertyManager来获取,比如轮速传感器、加速度传感器等。这时候就要用CarSensorManager了。

说白了,CarSensorManager专门处理那些高频、实时的传感器数据。它的数据更新频率可以很高,适合做驾驶辅助类的功能。

9.2.1 获取CarSensorManager

CarSensorManager sensorManager = 
    (CarSensorManager) car.getCarManager(Car.SENSOR_SERVICE);

9.2.2 读取传感器数据

以读取轮速传感器为例:

// 注册传感器监听
sensorManager.registerListener(
    new CarSensorEventCallback() {
        @Override
        public void onSensorChanged(SensorEvent event) {
            if (event.sensorType == 
                CarSensorManager.SENSOR_TYPE_WHEEL_SPEED) {
                float[] values = event.floatValues;
                // values[0] 左前轮速
                // values[1] 右前轮速
                // values[2] 左后轮速
                // values[3] 右后轮速
                updateWheelSpeedUI(values);
            }
        }
    },
    CarSensorManager.SENSOR_TYPE_WHEEL_SPEED,
    CarSensorManager.SENSOR_RATE_NORMAL  // 更新频率
);

经验之谈:传感器数据的更新频率有几种选择:SENSOR_RATE_FASTEST(最快)、SENSOR_RATE_NORMAL(正常)、SENSOR_RATE_UI(适合UI更新)。我建议UI展示用SENSOR_RATE_UI,否则更新太快反而导致界面卡顿。

9.3 车辆设置面板开发实战

好了,理论知识讲完了。咱们来做一个实际的车辆设置面板。这个面板包含车速显示、油量显示、空调控制三个模块。

9.3.1 整体架构

先画一张架构图,看看各个模块怎么配合:

车辆设置面板架构图 车辆数据层 CarPropertyManager | CarSensorManager | Vehicle HAL 提供车速、油量、空调、传感器等原始数据 业务逻辑层 数据解析 | 单位转换 | 状态管理 | 权限检查 将原始数据转换为UI可用的格式 UI展示层 车速显示模块 实时车速 | 单位切换 速度警告 油量显示模块 剩余油量 | 续航里程 低油量警告 空调控制模块 温度调节 | 风量控制 模式切换

9.3.2 核心代码实现

下面是一个简化版的车辆设置面板实现。我把它分成了三个模块:

public class VehicleSettingsFragment extends Fragment {
    
    private CarPropertyManager propertyManager;
    private CarSensorManager sensorManager;
    
    // UI组件
    private TextView speedText;
    private TextView fuelText;
    private TextView tempText;
    private SeekBar tempSeekBar;
    
    @Override
    public View onCreateView(LayoutInflater inflater, 
                             ViewGroup container, 
                             Bundle savedInstanceState) {
        View view = inflater.inflate(
            R.layout.fragment_vehicle_settings, 
            container, false);
        
        initUI(view);
        initCarServices();
        
        return view;
    }
    
    private void initUI(View view) {
        speedText = view.findViewById(R.id.speed_text);
        fuelText = view.findViewById(R.id.fuel_text);
        tempText = view.findViewById(R.id.temp_text);
        tempSeekBar = view.findViewById(R.id.temp_seekbar);
        
        // 空调温度调节
        tempSeekBar.setOnSeekBarChangeListener(
            new SeekBar.OnSeekBarChangeListener() {
                @Override
                public void onProgressChanged(
                    SeekBar seekBar, 
                    int progress, 
                    boolean fromUser) {
                    if (fromUser) {
                        // 将进度转换为温度值(16-30度)
                        float temp = 16 + (progress / 100f) * 14;
                        setAcTemperature(temp);
                    }
                }
            });
    }
    
    private void initCarServices() {
        Car car = Car.createCar(getContext());
        propertyManager = (CarPropertyManager) 
            car.getCarManager(Car.PROPERTY_SERVICE);
        sensorManager = (CarSensorManager) 
            car.getCarManager(Car.SENSOR_SERVICE);
        
        // 注册数据更新回调
        registerPropertyCallbacks();
    }
    
    private void registerPropertyCallbacks() {
        // 车速回调
        propertyManager.registerCallback(
            new CarPropertyManager.CarPropertyEventCallback() {
                @Override
                public void onChangeEvent(
                    CarPropertyValue value) {
                    float speed = (float) value.getValue();
                    getActivity().runOnUiThread(() -> 
                        speedText.setText(
                            String.format("%.1f km/h", speed)));
                }
                
                @Override
                public void onErrorEvent(
                    int propertyId, int zone) {
                    // 处理错误
                }
            },
            VehiclePropertyIds.PERF_VEHICLE_SPEED, 
            0);
        
        // 油量回调
        propertyManager.registerCallback(
            new CarPropertyManager.CarPropertyEventCallback() {
                @Override
                public void onChangeEvent(
                    CarPropertyValue value) {
                    float fuel = (float) value.getValue();
                    getActivity().runOnUiThread(() -> 
                        fuelText.setText(
                            String.format("%.1f L", fuel)));
                }
            },
            VehiclePropertyIds.FUEL_LEVEL, 
            0);
    }
    
    private void setAcTemperature(float temp) {
        try {
            propertyManager.setProperty(
                Float.class,
                VehiclePropertyIds.HVAC_TEMPERATURE_SET,
                VehicleArea.ZONE_ROW_1,
                temp);
            tempText.setText(
                String.format("%.1f°C", temp));
        } catch (SecurityException e) {
            // 权限不足时的处理
            Toast.makeText(getContext(), 
                "没有空调控制权限", 
                Toast.LENGTH_SHORT).show();
        }
    }
}

9.3.3 权限声明

别忘了在AndroidManifest里声明需要的权限:

<uses-permission 
    android:name="android.car.permission.CAR_SPEED" />
<uses-permission 
    android:name="android.car.permission.CAR_ENERGY" />
<uses-permission 
    android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
<uses-permission 
    android:name="android.car.permission.CAR_SENSOR" />

避坑指南:我曾经在项目里漏掉了CAR_SENSOR权限,结果传感器回调一直不触发。调试了半天才发现是权限问题。记住,每个数据源都要检查对应的权限。

9.4 常见问题与最佳实践

问题 原因 解决方案
数据更新不及时 回调注册频率太低 使用SENSOR_RATE_FASTEST或缩短轮询间隔
权限异常 缺少对应权限声明 检查AndroidManifest和运行时权限
UI卡顿 数据更新频率过高 使用SENSOR_RATE_UI或做数据降频
车辆熄火后数据异常 车辆进入低功耗模式 监听车辆状态,熄火后停止数据请求

嗯,最后说一句:车载开发跟手机开发最大的不同,就是数据源的不确定性。你永远不知道下一台测试车会给你返回什么奇怪的数据。所以,做好数据校验和异常处理,比什么都重要。

今天的分享就到这里。记住,多测试几台车,多踩几个坑,经验就积累起来了。


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