【Android R】车载 Android 核心服务 - CarPropertyService

座舱配图 - 上汽飞凡R7

前言

对比开发车载Android和手机Android应用,最大的区别应该就是许多车载应用需要考虑汽车整体的运行状态,例如,控制车载空调或车速达到一定的阈值时,出于安全的考虑多媒体应用要主动保持静音;汽车处于行驶状态下,OTA应用要保持静默等等。APP如何从Framework层获取车辆状态的数据,而Framework层又是从哪里获取到数据,它们的运行机制是怎样的,就是本篇要解释的问题了。

本文是车载Android核心服务系列文章的第二篇,系列目录如下:

1.【Android R】车载 Android 核心服务 - CarService 解析

2.【Android R】车载 Android 核心服务 - CarPropertyService 解析

这个系列文章的主要目的在于整理原生车载Android系统中,一些核心Service的运行原理与源码实现,所以会有大段的源码解读,内容会比较枯燥。如果阅读本文时还没有车载或车载相关的经验并不丰富,建议先阅读从应用工程师的角度再谈车载 Android 系统,了解车载Android系统的基础结构。

本系列涉及的应用层API以及Framework层的实现方式,基于原生车载Android R系统,由于实际项目中各个主机厂商会对CarService做种种修改,本系列内容仅供车载开发者参考。

CarPropertyService 简介

通过上一篇的介绍,我们了解了狭义上的CarService其实只是一系列Binder对象的容器,本身并没有多少特殊功能,有过车载经验的同学可能会有疑问,因为这可能和你正在经历的项目有出入,这是因为部分量产型的车载项目为了保证关键服务的稳定性,CarService中不少功能都被独立出去了,导致CarService实际上只能提供了查询、设置车辆属性的功能,而这部分功能就是本篇的主角 - CarPropertyService实现的。

从实现上来说CarPropertyService的架构如下:

我们从下往上依次来介绍:

  • VehicleHAL

用于接收MCU数据的HAL层程序。VehicleHAL与MCU之间是如何进行通信的,每个车载项目技术选型不同,实现上也千差万别,无法详细介绍。我个人经历过得的某个车载项目是使用DBUS

  • HalClient

HIDL在Client端的HwBinder对象,实现最基本的HIDL通信功能。

  • VehicleHal

用于与HAL层的Vehicle HAL程序的通信接口。它需要对接收到的数据进行基本解析(类型检查),然后将每个事件发送到相应的HalServiceBase实现类里。

由于Framework层的VehicleHal与HAL层的VehicleHAL存在重名,后面为了区分会用VehicleHal(FWK) 表示Framework层的VehicleHal,用VehicleHAL(HAL) 表示HAL层的VehicleHAL。

  • PropertyHalService

负责进一步处理来自VehicleHal(FWK)数据的接口。是HalServiceBase的实现类。

  • CarPropertyService

ICarProperty.aidl的实现类。是应用层与HAL层的通信中继。

  • CarPropertyManager

CarPropertyService在Client端的代理。车载系统中的应用需要通过CarPropertyManager来获取或设置车辆的属性。

先来看 CarPropertyManager 提供的API。

车辆属性 API

在Android R中CarInfoManagerCarCabinManagerCarHvacManagerCarSensorManagerCarVendorExtensionManager均已经过时,在我个人实际经历的车载项目中,负责开发CarService的同事,也会选择将以上Manager移除使用CarPropertyManager替代。

虽然将汽车的Property属性分散到独立的Manager中可以让Car API的易用性、可读性更强,但是随着汽车属性的不断增加,API的维护也会变得愈加复杂,而CarPropertyManager从实现上就让维护工作变得简单,Google可能也是基于以上的考虑选择不再维护独立的Manager。

所以本文不再介绍CarInfoManagerCarCabinManagerCarHvacManagerCarSensorManagerCarVendorExtensionManager的实现,有需要的同学请参考源码中是如何实现的。

CarPropertyManager API 介绍

CarPropertyManager 中定义的常量

类型 常量名
int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED 表示设置操作失败的状态,汽车拒绝访问。
int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG 表示设置操作失败的状态,参数无效。
int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE 表示设置操作失败的状态,属性不可用。
int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN 表示设置操作失败的状态,重新尝试。
int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN 表示设置操作失败的状态,未知错误。
float SENSOR_RATE_FAST 以10Hz的速率读取传感器。
float SENSOR_RATE_FASTEST 以100Hz的速率读取传感器。
float SENSOR_RATE_NORMAL 以1Hz的速率读取传感器。
float SENSOR_RATE_ONCHANGE 读取ON_CHANGE传感器
float SENSOR_RATE_UI 以5Hz的速率读取传感器。

CarPropertyManager 中定义的方法。

返回值类型 方法名
int getAreaId(int propId, int area) 返回包含车辆属性选定区域的areaId。
boolean getBooleanProperty(int prop, int area) 返回bool类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
CarPropertyConfig<?> getCarPropertyConfig(int propId) 按属性Id获取CarPropertyConfig。
float getFloatProperty(int prop, int area) 返回float类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
int[] getIntArrayProperty(int prop, int area) 返回int数组类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
int getIntProperty(int prop, int area) 返回int类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
<E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int areaId) 返回CarPropertyValue类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
<E> CarPropertyValue<E> getProperty(int propId, int areaId)
List<CarPropertyConfig> getPropertyList(ArraySet<Integer> propertyIds)
List<CarPropertyConfig> getPropertyList()
boolean isPropertyAvailable(int propId, int area) 根据汽车的当前状态,检查给定属性是否可用或禁用。
boolean registerCallback(CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float rate) 注册CarPropertyEventCallback以获取车辆属性更新。
void setBooleanProperty(int prop, int areaId, boolean val) 修改属性。
void setFloatProperty(int prop, int areaId, float val) 设置float类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
void setIntProperty(int prop, int areaId, int val) 设置int类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
<E> void setProperty(Class<E> clazz, int propId, int areaId, E val) 按areaId设置车辆属性的值。
void unregisterCallback(CarPropertyManager.CarPropertyEventCallback callback) 停止监听车辆属性的更新回调
void unregisterCallback(CarPropertyManager.CarPropertyEventCallback callback, int propertyId) 停止监听车辆属性的更新回调

setXXXProperty/getXXXProperty默认只实现了对float、int、boolean、intArray类型的拓展,如需要使用更多的类型,可以使用setProperty/getProperty(Class<T> class)传入需要拓展的类型即可。

CarPropertyManager 的实现原理并不复杂,可以直接参考源码:/packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java

CarPropertyConfig API 介绍

CarPropertyConfig表示有关汽车属性的一般信息,例如汽车区域的数据类型和最小/最大范围(如果适用)。也是实际开发中非常常用的类。

CarPropertyConfig 中定义的常量。

类型 常量名
int VEHICLE_PROPERTY_ACCESS_NONE 属性访问权限未知
int VEHICLE_PROPERTY_ACCESS_READ 该属性是可读的
int VEHICLE_PROPERTY_ACCESS_READ_WRITE 该属性是可读、可写的
int VEHICLE_PROPERTY_ACCESS_WRITE 该属性是可写的
int VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS 这种属性值会以一定的频率不断上报
int VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE 该属性的值会在发生变化时上报
int VEHICLE_PROPERTY_CHANGE_MODE_STATIC 该属性的值始终不会改变

CarPropertyConfig 中定义的方法

返回值类型 方法名
int getAccess() 返回汽车属性的访问类型。具体类型就是上面定义的前4个常量
int[] getAreaIds() 返回汽车的区域id数组
int getAreaType() 返回汽车属性的区域类型。
int getChangeMode() 返回汽车属性的更改模式。具体模式就是上面定义的后3个常量
List<Integer> getConfigArray() 返回额外的配置属性
float getMaxSampleRate() 返回最大频率。仅支持持续上报的属性
float getMinSampleRate() 返回最小频率。仅支持持续上报的属性
T getMaxValue(int areaId)
T getMaxValue()
T getMinValue()
T getMinValue(int areaId)
int getPropertyId() 返回属性ID
Class<T> getPropertyType() 返回车辆属性的类型
boolean isGlobalProperty() 返回 是否是全局属性

CarPropertyManager 使用示例

使用CarPropertyManager可以分为以下几个步骤:

1)使用Car连接到 CarService ,并获取到 CarPropertyManager

Car car = Car.createCar(this, workThreadHandler, 2000, new Car.CarServiceLifecycleListener() {
    @Override
    public void onLifecycleChanged(@NonNull Car car, boolean ready) {
        // ready 在Service断开连接时会变为false
if (ready) {
            CarPropertyManager propertyMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
           
        } else {
            // CarService 发生异常或连接被断开了,需要client端处理。
}
    }
});

2)给所有的property属性注册监听事件

 // 空调的property id list,需要看hal层是如何定义的
 private final ArraySet<Integer> mHvacPropertyIds = new ArraySet<>(Arrays.asList(new Integer [] {
            ...
    }));


CarPropertyManager propertyMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
List<CarPropertyConfig> propertyList = propertyMgr.getPropertyList(mHvacPropertyIds);
for (CarPropertyConfig config : propertyList) {
    // 给每个单独的propertyId注册监听回调。
propertyMgr.registerCallback(callback,config.getPropertyId(), SENSOR_RATE_ONCHANGE);
}

3)获取单个Property的值

public boolean getBooleanProperty(@PropertyId int propertyId, int area) {
    return propertyMgr.getBooleanProperty(propertyId, area);
}

虽然使用getBooleanProperty、getIntProperty、getFloatProperty、getIntArrayProperty代码上更简洁一些,但是更建议使用getProperty()getProperty()的返回值是CarPropertyValue,这其中包含了Property的状态信息,可以让使用方覆盖更多的异常场景。

当属性不可用时,getXXXProperty()会返回默认的值,造成使用方读取数据不准确。

public CarPropertyValue<Boolean> getBooleanProperty(int propertyId, int area) {
    return propertyMgr.getProperty(Boolean.class, propertyId, area);
}

CarPropertyValue<Boolean> value = getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, 0);
if (value == null && value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
    // ac 不可用
} else if (value.getValue()) {
    // ac 开
} else {
    // ac 关
}

4)设定单个Property的值

public void setBooleanProperty(@PropertyId int propertyId, int area, boolean val) {
    if (mHvacPropertyIds.contains(propertyId)) {
        propertyMgr.setBooleanProperty(propertyId, area, val);
    }
}

设定的值最终会通过aidl接口,将数据传输到CarPropertyService中,接下来我们继续看数据在CarPropertyService中是如何传递的。

CarPropertyService 实现原理

CarPropertyService 初始化流程

CarPropertyService是在CarService中完成创建的,CarService的初始化流程在之前的文章【Android R】车载 Android 核心服务 - CarService 解析中已经有过介绍,不再赘述。CarPropertyService的初始流程分为以下4步:

1)首先,在ICarImpl中创建VehicleHal(FWK);

@VisibleForTesting
ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
         CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
         @Nullable CarUserService carUserService,
         @Nullable CarWatchdogService carWatchdogService) {
    ...
    mHal = new VehicleHal(serviceContext, vehicle);
    // 在任何其他服务组件之前执行此操作,以允许进行功能检查。即使没有初始化,它也应该工作。
 // 为此,vhal-get会被重试,因为它可能太早了。
VehiclePropValue disabledOptionalFeatureValue = mHal.getIfAvailableOrFailForEarlyStage(
            VehicleProperty.DISABLED_OPTIONAL_FEATURES, INITIAL_VHAL_GET_RETRY);
    String[] disabledFeaturesFromVhal = null;
    if (disabledOptionalFeatureValue != null) {
        String disabledFeatures = disabledOptionalFeatureValue.value.stringValue;
        if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
            disabledFeaturesFromVhal = disabledFeatures.split(",");
        }
    }
    if (disabledFeaturesFromVhal == null) {
        disabledFeaturesFromVhal = new String[0];
    }
    ...
}

2)在 VehicleHal(FWK) 创建过程中,同时创建出 PropertyHalService 和 HalClient;

public VehicleHal(Context context, IVehicle vehicle) {
    ...
    mPropertyHal = new PropertyHalService(this);
    ...
mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/ );
}

3)然后,在ICarImpl中创建 CarPropertyService;

ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
         CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
         @Nullable CarUserService carUserService,
         @Nullable CarWatchdogService carWatchdogService) {
    ...
    mCarPropertyService = new CarPropertyService(serviceContext, mHal.getPropertyHal());
    ...
}

4)最后,在 ICarImpl 中调用 VehicleHal.init() CarPropertyService.init() 完成初始化。

@MainThread
void init() {
    mHal.init();
    for (CarServiceBase service : mAllServices) {
        service.init();
    }
}

接下来,我们依次把这些模块是如何实现数据上报的流程梳理一下,先来看处于Framework最底层的 HalClient。


HalClient

车辆HAL客户端。直接与车辆HAL的HIDL接口IVehicle交互。包含一些可检索属性的逻辑,将车辆通知重定向到给定的looper线程中。

HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback,
          int waitCapMs, int sleepMs) {
    mVehicle = vehicle;
    Handler handler = new CallbackHandler(looper, callback);
    mInternalCallback = new VehicleCallback(handler);
    mWaitCapMs = waitCapMs;
    mSleepMs = sleepMs;
}

VehicleCallback 是HIDL接口IVehicleCallback.Stub的实现类,负责监听HAL层上报的数据,然后将其发送到CallbackHandler中进行处理。

    private static final class VehicleCallback extends IVehicleCallback.Stub {
        private final Handler mHandler;

        VehicleCallback(Handler handler) {
            mHandler = handler;
        }

        @Override
        public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
            mHandler.sendMessage(Message.obtain(
                    mHandler, CallbackHandler.MSG_ON_PROPERTY_EVENT, propValues));
        }

        @Override
        public void onPropertySet(VehiclePropValue propValue) {
            mHandler.sendMessage(Message.obtain(
                    mHandler, CallbackHandler.MSG_ON_PROPERTY_SET, propValue));
        }

        @Override
        public void onPropertySetError(int errorCode, int propId, int areaId) {
            mHandler.sendMessage(Message.obtain(
                    mHandler, CallbackHandler.MSG_ON_SET_ERROR,
                    new PropertySetError(errorCode, propId, areaId)));
        }
    }

CallbackHandler是一个自定义的Handler,会将VehicleHal(HAL)上报的数据分类通过callback回调给VehicleHal(FWK)。

private static final class CallbackHandler extends Handler {
    private static final int MSG_ON_PROPERTY_SET = 1;
    private static final int MSG_ON_PROPERTY_EVENT = 2;
    private static final int MSG_ON_SET_ERROR = 3;
    ...
    @Override
    public void handleMessage(Message msg) {
        IVehicleCallback callback = mCallback.get();
        ...
        try {
            switch (msg.what) {
                case MSG_ON_PROPERTY_EVENT:
                    callback.onPropertyEvent((ArrayList<VehiclePropValue>) msg.obj);
                    break;
                case MSG_ON_PROPERTY_SET:
                    callback.onPropertySet((VehiclePropValue) msg.obj);
                    break;
                case MSG_ON_SET_ERROR:
                    PropertySetError obj = (PropertySetError) msg.obj;
                    callback.onPropertySetError(obj.errorCode, obj.propId, obj.areaId);
                    break;
                default:
                    Log.e(TAG, "Unexpected message: " + msg.what);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Message failed: " + msg.what);
        }
    }
}

思考一个问题,为什么HAL上报的数据信息要先经过Handler再处理呢?

这既有线程切换的考虑,还有就是VehicleHAL(HAL)上报的数据有时会非常频繁,将数据放到Looper的MessageQueue中可以便于我们按照上报的顺序,有序地处理数据。

VehicleHal

用于与HAL层的Vehicle HAL程序的通信接口。将HalClient回调过来的数据,进行初步的处理。我们以onPropertyEvent为例,看一下VehicleHAl(FWK)是怎么处理HalClient回调过来的数据的。

VehicleHAl(FWK)的处理方式分为两步

1)第一步,根据上报数据找到对应的HalServiceBase(HalServiceBase是PropertyHalService的父类),将VehiclePropValue添加到PropertyHalService的list中。

@Override
public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
    synchronized (mLock) {
        for (VehiclePropValue v : propValues) {
            HalServiceBase service = mPropertyHandlers.get(v.prop);
            if(service == null) {
                Log.e(CarLog.TAG_HAL, "HalService not found for prop: 0x"
                        + toHexString(v.prop));
                continue;
            }
            service.getDispatchList().add(v);
            mServicesToDispatch.add(service);
            ...
        }
    }
    ...
}

2)第二步,主动触发PropertyHalService.onHalEvents()将VehiclePropValue发送到PropertyHalService中,紧接着清理掉缓存数据。

@Override
public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
    ...
    for (HalServiceBase s : mServicesToDispatch) {
        s.onHalEvents(s.getDispatchList());
        s.getDispatchList().clear();
    }
    mServicesToDispatch.clear();
}

PropertyHalService

PropertyHalService.onHalEvents中处理接收到的value list。将数据转换为CarPropertyValue后,通过PropertyHalListener将处理好的数据回调给CarPropertyService,而最终会由CarPropertyService将数据回调会应用层的接口。

@Override
public void onHalEvents(List<VehiclePropValue> values) {
    PropertyHalListener listener;
    ...
    if (listener != null) {
        for (VehiclePropValue v : values) {
            if (v == null) {
                continue;
            }
            ...
            int mgrPropId = halToManagerPropId(v.prop);
            CarPropertyValue<?> propVal;
            if (isMixedTypeProperty(v.prop)) {
                // parse mixed type property value.
VehiclePropConfig propConfig;
                synchronized (mLock) {
                    propConfig = mHalPropIdToVehiclePropConfig.get(v.prop);
                }
                boolean containBooleanType = propConfig.configArray.get(1) == 1;
                propVal = toMixedCarPropertyValue(v, mgrPropId, containBooleanType);
            } else {
                propVal = toCarPropertyValue(v, mgrPropId);
            }
            // 封装到 CarPropertyEvent
CarPropertyEvent event = new CarPropertyEvent(
                    CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
            mEventsToDispatch.add(event);
        }
        listener.onPropertyChange(mEventsToDispatch);
        mEventsToDispatch.clear();
    }
}

注意两个方法,toMixedCarPropertyValue()toCarPropertyValue() 如果数据类型是Integer、Float、Long、Float[]、Long[]、Integer[]、byte[]、String则由 toCarPropertyValue()负责数据转换。除此以外的类型由toMixedCarPropertyValue()负责数据转换。它们都是将VehiclePropValue转换为CarPropertyValue


设定/获取 Property

设定和与获取Property的流程并不复杂,这里就不再粘贴源码了逐个讲解了,贴上一份设定的时序图。


权限控制

上面在分析CarPropertyService监听属性变化的具体实现时,我们提到了使用Car API的接口需要注册对应的权限,那么这些权限是如何管理的呢?

在PropertyHalService的构造方法中,创建了一个PropertyHalServiceIds的对象,而这个对象就是用来存储每个属性所需要的权限的。

PropertyHalServiceIds源码位置:/packages/services/Car/service/src/com/android/car/hal/PropertyHalServiceIds.java

public PropertyHalService(VehicleHal vehicleHal) {
    mPropIds = new PropertyHalServiceIds();
    mSubscribedHalPropIds = new HashSet<Integer>();
    mVehicleHal = vehicleHal;
}

在PropertyHalServiceIds的构造方法中,将每个属性对应需要的权限进行了一一关联,保存在一个SparseArray中。


那么接下来我们以getProperty()方法为例,看一下是如何限制无权限应用的调用的。

@Override
public CarPropertyValue getProperty(int prop, int zone) {
    ...
    ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop));
    return mHal.getProperty(prop, zone);
}

@Nullable
public String getReadPermission(int mgrPropId) {
    int halPropId = managerToHalPropId(mgrPropId);
    return mPropIds.getReadPermission(halPropId);
}

@Nullable
public String getReadPermission(int propId) {
    Pair<String, String> p = mProps.get(propId);
    if (p != null) {
        // 属性ID存在。返回 权限。
if (p.first == null) {
            Log.e(TAG, "propId is not available for reading : 0x" + toHexString(propId));
        }
        return p.first;
    } else if (isVendorProperty(propId)) {
        // 如果属性是供应商属性,并且没有特定权限。
return Car.PERMISSION_VENDOR_EXTENSION;
    } else {
        return null;
    }
}

getReadPermission的调用链很好懂,主要就是将传入的id与PropertyHalServiceIds中关联好的权限比对,并取出对应的权限字符串。

assertPermission方法会判断调用方是否拥有相应的权限,或本次调用是否是自身发起的,如果不是,则会抛出SecurityException。

#########ICarImpl.java###############
public static void assertPermission(Context context, String permission) {
    if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
        throw new SecurityException("requires " + permission);
    }
}

setProperty()方法的权限检查与getProperty()方法类似,不过getProperty()方法是检查ReadPermission,setProperty()方法是检查WritePermission。

车辆属性的读取以及操作需要慎重授权给应用,所以权限控制在车载Android系统中就显得尤为重要。

VehicleHAL

VehicleHAL 是由Android Automotive OS定义的硬件抽象层(hardware abstract layer)。它定义了 OEM 可以实现的属性以及与Framework Service交互的接口。用户对车辆APP产生的一系列操作最终都会来到VehicleHAL,并由于VehicleHAL转发出Android系统。

源码地址:/hardware/interfaces/automotive/vehicle/2.0/

源码结构

VehicleHAL主要由IPC接口交互逻辑两部分组成,如下所示

  • IVehicle.hal

定义VehicleHAL对外暴露的方法。

  • IVehicleCallback.hal

定义属性变化时的回调接口

  • types.hal

定义在IVehicle.halIVehicleCallback.hal中使用的数据结构和属性值。

上述三个hal文件的结构与AIDL的通信结构非常相似,不过这种通信方式叫做HIDL(读作:嗨豆)。

有关HIDL的进一步内容,请参考官方文档:https://source.android.google.cn/docs/core/architecture/hidl

  • default/ utils/

是Android Automotive OS对于VechicleHAL的参考实现。但是其中并没有实现与车辆总线进行数据交互这样的业务逻辑,这块的内容需要主机制造商自行实现。

VehicleHAL 接口

VHAL 支持以下接口:

  • getAllPropConfigs() generates (vec<VehiclePropConfig> propConfigs);

列出 VehicleHAL 所支持的所有属性的配置。CarService 仅使用支持的属性。

  • getPropConfigs(vec<int32_t> props) generates (StatusCode status, vec<VehiclePropConfig> propConfigs);

返回所选属性的配置。

  • get(VehiclePropValue requestedPropValue) generates (StatusCode status, VehiclePropValue propValue);

获取车辆属性值。

  • set(VehiclePropValue propValue) generates (StatusCode status);

向属性写入一个值。写入的结果是按属性进行定义的。

  • subscribe(IVehicleCallback callback, vec<SubscribeOptions> options) generates (StatusCode status);

开始监视属性值的变化。

  • unsubscribe(IVehicleCallback callback, int32_t propId) generates (StatusCode status);

取消订阅属性事件。

VHAL 支持以下回调接口:

  • oneway onPropertyEvent(vec<VehiclePropValue>propValues);

通知车辆属性值的变化。应只针对已订阅属性执行。

  • oneway onPropertySet(VehiclePropValue propValue);

如果客户端使用SubscribeFlags.EVENTS_FROM_ANDROID标志订阅了属性,并且调用了IVehicle.set()方法,则会调用此方法。

  • oneway onPropertySetError(StatusCode errorCode,int32_t propId,int32_tareaId);

返回全局 VHAL 级错误或每个属性的错误。全局错误会导致 HAL 重新启动,这可能会导致包括应用在内的其他组件重新启动。

编译VehicleHAL

HIDL接口定义好之后,与AIDL接口一样需要编译更jar提供给Framework的开发,以下步骤是编译原生的VehicleHAL,实际项目中的VehicleHAL一般会放置vendor里面,但是编译方式一样。

cd hardware/interfaces/automotive/vehicle/2.0
mma

编译好的jar包位于

/out/soong/.intermediates/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle-V2.0-java/android_common/javac

如下图所示:


由于博主并没有实际从事过HAL层的开发,有关VehicleHAL就只介绍到这里,实际工作中一般会有单独负责HAL层的同事编写这里的代码。

更多内容请参考官方的文档:https://source.android.google.cn/docs/devices/automotive/vhal

总结

本篇介绍了CarPropertyService的实现原理,但是仅通过阅读这篇文章其实并不能完全掌握整个CarPropertyService,这是任何技术性文章都做不到的通病,实际项目依然需要我们仔细阅读源码,分析方法的含义,本篇文章的实际目的是让你弄清楚关键节点的实现方式和运行原理。

我个人也负责过CarPropertyService的开发工作,当时由于对CarPropertyService的运行机制并不了解,选择整个重写CarPropertyService,现在想想着实走了不少弯路。

好了,感谢你的阅读,希望能帮助到你。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容