前言
对比开发车载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中CarInfoManager
、CarCabinManager
、CarHvacManager
、CarSensorManager
、CarVendorExtensionManager
均已经过时,在我个人实际经历的车载项目中,负责开发CarService
的同事,也会选择将以上Manager移除使用CarPropertyManager
替代。
虽然将汽车的Property属性分散到独立的Manager中可以让Car API的易用性、可读性更强,但是随着汽车属性的不断增加,API的维护也会变得愈加复杂,而CarPropertyManager
从实现上就让维护工作变得简单,Google可能也是基于以上的考虑选择不再维护独立的Manager。
所以本文不再介绍CarInfoManager
、CarCabinManager
、CarHvacManager
、CarSensorManager
、CarVendorExtensionManager
的实现,有需要的同学请参考源码中是如何实现的。
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类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。 |
CarPropertyValue | getProperty(Class clazz, int propId, int areaId) 返回CarPropertyValue类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。 |
CarPropertyValue | getProperty(int propId, int areaId) |
List | getPropertyList(ArraySet propertyIds) |
List | 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类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。 |
void | setProperty(Class 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 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 | getConfigArray() 返回额外的配置属性 |
| float | getMaxSampleRate() 返回最大频率。仅支持持续上报的属性 |
| float | getMinSampleRate() 返回最小频率。仅支持持续上报的属性 |
| T | getMaxValue(int areaId) |
| T | getMaxValue() |
| T | getMinValue() |
| T | getMinValue(int areaId) |
| int | getPropertyId() 返回属性ID |
| Class | 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系统。
源码结构
VehicleHAL主要由IPC接口和交互逻辑两部分组成,如下所示
- IVehicle.hal
定义VehicleHAL对外暴露的方法。
- IVehicleCallback.hal
定义属性变化时的回调接口
- types.hal
定义在IVehicle.hal
与IVehicleCallback.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 propConfigs);
列出 VehicleHAL 所支持的所有属性的配置。CarService 仅使用支持的属性。
- getPropConfigs(vec props) generates (StatusCode status, vec propConfigs);
返回所选属性的配置。
- get(VehiclePropValue requestedPropValue) generates (StatusCode status, VehiclePropValue propValue);
获取车辆属性值。
- set(VehiclePropValue propValue) generates (StatusCode status);
向属性写入一个值。写入的结果是按属性进行定义的。
- subscribe(IVehicleCallback callback, vec options) generates (StatusCode status);
开始监视属性值的变化。
- unsubscribe(IVehicleCallback callback, int32_t propId) generates (StatusCode status);
取消订阅属性事件。
VHAL 支持以下回调接口:
- oneway onPropertyEvent(vecpropValues);
通知车辆属性值的变化。应只针对已订阅属性执行。
- 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
,现在想想着实走了不少弯路。
好了,感谢你的阅读,希望能帮助到你。