从车窗升降一探 Android 车机的重要 API:车辆属性 CarProperty
最佳答案 问答题库518位专家为你答疑解惑
前言
前面我们介绍过 Android 车机 Automotive OS 的几块重要内容:
- 一文了解 Android 车机如何处理中控的旋钮输入
- 从实体按键看 Android 车机的自定义事件机制
- 深度入门 Android 车机核心 CarService 的构成和链路
本篇文章我们聚焦 Android 车机上最重要、最常用的接口:即车辆属性 CarPropertyManager
。
并结合车窗升降这种典型的场景来探究它的完整链路。
实现车窗升降
CarPropertyManager
通常针对某个 Property
发起读写,这些属性有很多,从车窗到空调、油量到续航等等。
想要控制它们,得需要知道它的唯一标识,并和系统定义的 ID 保持一致。那么车窗对应的 ID 为 VehiclePropertyIds
中的 WINDOW_POS
,其要求 app 拥有专用的权限:
android.car.Car.PERMISSION_CONTROL_CAR_WINDOWS
属性监听
当目标属性发生变化,可以通过 CarPropertyEventCallback
通知到请求 App,为了满足各种场景,系统提供了设置通知频次的可能。
总共有如下几种:
对于车窗、入座这些即时信号,采用 SENSOR_RATE_ONCHANGE 类型即可,意味着只在变化的时候通知。当然,注册的时候会立即回调一次以通知当前的数值。
代码很简单,构建 CarPropertyEventCallback 实例,并传递目标 Property ID 和上述的通知类型,即可完成该属性的监听。
class CarEventCallBack: CarPropertyManager.CarPropertyEventCallback {override fun onChangeEvent(value: CarPropertyValue<*>?) { }
}val car = Car.createCar(context)
val carPropertyManager =car?.getCarManager(Car.PROPERTY_SERVICE) as CarPropertyManagercarPropertyManager.registerCallback(CarEventCallBack(),VehiclePropertyIds.WINDOW_POS,CarPropertyManager.SENSOR_RATE_ONCHANGE
)
属性读写
对于车窗硬件来说,用户关心的是其升降的状况,系统用 0~100 来进行定义,继而决定了它的值为 Int 型。
那么读取的 API 为 getIntProperty()
,参数:
- prop:希望读取的属性 ID,比如上面的车窗 Property ID:WINDOW_POS
- area:希望读取属性的位置信息 zone,对应到
VehicleAreaWindow
类型中常量
注意:该方法是同步的,而且因为车窗等属性的操作耗时,建议在子线程 invoke。
写入的 API 为 setIntProperty()
,参数:
- prop:希望改写的属性 ID,
- areaId:该属性对应的位置薪资
- val:Value to set,比如车窗即 0 ~ 100,对应着完全打开到完全关闭
和 getIntProperty() 一样,set 一样耗时,需要同样运行在子线程中。
系统预设的和 Window 相关的 zone areaId 如下,比如前排、驾驶侧、副驾驶侧、乘客侧、天窗、挡风玻璃等。
package android.hardware.automotive.vehicle;public @interface VehicleAreaWindow {public static final int FRONT_WINDSHIELD = 1;public static final int REAR_WINDSHIELD = 2;public static final int ROW_1_LEFT = 16;public static final int ROW_1_RIGHT = 64;public static final int ROW_2_LEFT = 256;public static final int ROW_2_RIGHT = 1024;public static final int ROW_3_LEFT = 4096;public static final int ROW_3_RIGHT = 16384;public static final int ROOF_TOP_1 = 65536;public static final int ROOF_TOP_2 = 131072;
}
如下代码展示如何了完全打开驾驶位车窗。
Thread().run {carPropertyManager.setIntProperty(VehiclePropertyIds.WINDOW_POS,VehicleAreaWindow.WINDOW_ROW_1_LEFT,0)
}
工作原理
首先,车窗相关的 area 在 HAL 层有相应的定义:
// android/hardware/automotive/vehicle/2.0/types.h /*** Various windshields/windows in the car.*/
enum class VehicleAreaWindow : int32_t {FRONT_WINDSHIELD = 1 /* 0x00000001 */,REAR_WINDSHIELD = 2 /* 0x00000002 */,ROW_1_LEFT = 16 /* 0x00000010 */,ROW_1_RIGHT = 64 /* 0x00000040 */,ROW_2_LEFT = 256 /* 0x00000100 */,ROW_2_RIGHT = 1024 /* 0x00000400 */,ROW_3_LEFT = 4096 /* 0x00001000 */,ROW_3_RIGHT = 16384 /* 0x00004000 */,ROOF_TOP_1 = 65536 /* 0x00010000 */,ROOF_TOP_2 = 131072 /* 0x00020000 */,
};
读取
直接看 getIntProperty()
,首先调用 checkSupportedProperty() 检查是否支持该属性,当不支持的话抛出:
IllegalArgumentException: “Unsupported property:xxx”
接着调用 getProperty()
,不过指定了返回的数据类型。
public class CarPropertyManager extends CarManagerBase {public int getIntProperty(int prop, int area) {checkSupportedProperty(prop);CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);return handleNullAndPropertyStatus(carProp, area, 0);}private void checkSupportedProperty(int propId) {switch (propId) {case VehiclePropertyIds.INITIAL_USER_INFO:case VehiclePropertyIds.SWITCH_USER:case VehiclePropertyIds.CREATE_USER:case VehiclePropertyIds.REMOVE_USER:case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:throw new IllegalArgumentException("Unsupported property: "+ VehiclePropertyIds.toString(propId) + " (" + propId + ")");}}...
}
getProperty() 的实现在于 CarPropertyService
。
public class CarPropertyManager extends CarManagerBase {public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) {checkSupportedProperty(propId);try {CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);if (propVal != null && propVal.getValue() != null) {Class<?> actualClass = propVal.getValue().getClass();}return propVal;}...}...
}
CarPropertyService
按照如下步骤进行:
-
先到存放所有 Property ID 的
SparseArray
中检查是否确实存在该 Property,如果不存在的话打印 error 提醒并结束 -
获取该 Property 的 permission 配置,如果不存在的话,抛出:
SecurityException: Platform does not have permission to read value for property Id: 0x…
-
assertPermission()
检查当前 CarService 是否确实被授予了如上 permission -
最后调用持有的
PropertyHalService
继续发出读取的调用
public class CarPropertyService extends ICarProperty.Stubimplements CarServiceBase, PropertyHalService.PropertyHalListener {@Overridepublic CarPropertyValue getProperty(int prop, int zone) ... {synchronized (mLock) {if (mConfigs.get(prop) == null) {// Do not attempt to register an invalid propIdSlogf.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));return null;}}// Checks if android has permission to read property.String permission = mHal.getReadPermission(prop);if (permission == null) {throw new SecurityException("Platform does not have permission to read value for "+ "property Id: 0x" + Integer.toHexString(prop));}CarServiceUtils.assertPermission(mContext, permission);return runSyncOperationCheckLimit(() -> {return mHal.getProperty(prop, zone);});}...
}
PropertyHalService 首先调用 managerToHalPropId() 将 Property ID 转为 HAL 中该 ID 的定义,并再度检查该 HAL ID 是否确实存在。如果不存在的话亦抛出:
IllegalArgumentException:Invalid property Id : 0x…
接着,通过 VehicleHal
传递 HAL 中 ID 继续读取得到 HalPropValue
,当读取的 value 存在的话,首先得获取该 Property 在 HAL 层和上层定义的 HalPropConfig
规则。
最后依据 config 将 value 解析成 CarPropertyValue
类型返回。
public class PropertyHalService extends HalServiceBase {
'/ ' ...public CarPropertyValue getProperty(int mgrPropId, int areaId)throws IllegalArgumentException, ServiceSpecificException {int halPropId = managerToHalPropId(mgrPropId);if (!isPropertySupportedInVehicle(halPropId)) {throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));}// CarPropertyManager catches and rethrows exception, no need to handle here.HalPropValue value = mVehicleHal.get(halPropId, areaId);if (value == null) {return null;}HalPropConfig propConfig;synchronized (mLock) {propConfig = mHalPropIdToPropConfig.get(halPropId);}return value.toCarPropertyValue(mgrPropId, propConfig);}...
}
其实 VehicleHal 并未做太多处理就直接交给了 HalClient 来处理。
public class VehicleHal implements HalClientCallback {...public HalPropValue get(int propertyId)throws IllegalArgumentException, ServiceSpecificException {return get(propertyId, NO_AREA);}...public HalPropValue get(int propertyId, int areaId)throws IllegalArgumentException, ServiceSpecificException {return mHalClient.getValue(mPropValueBuilder.build(propertyId, areaId));}...
}
HalClient
通过 invokeRetriable()
进行超时为 50ms 的 internalGet()
调用:如果结果是 TRY_AGAIN 并且尚未超时的话,再次调用;反之已经超时或者结果成功获取到的话,即结束。
后续会再次检查该 Result 中的 status,是否是不合法的、空的值等等,通过检查的话则返回 HalPropValue
出去。
final class HalClient {...private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;HalPropValue getValue(HalPropValue requestedPropValue)throws IllegalArgumentException, ServiceSpecificException {ObjectWrapper<ValueResult> resultWrapper = new ObjectWrapper<>();resultWrapper.object = new ValueResult();int status = invokeRetriable(() -> {resultWrapper.object = internalGet(requestedPropValue);return resultWrapper.object.status;}, mWaitCapMs, mSleepMs);ValueResult result = resultWrapper.object;if (StatusCode.INVALID_ARG == status) {throw new IllegalArgumentException(getValueErrorMessage("get", requestedPropValue, result.errorMsg));}if (StatusCode.OK != status || result.propValue == null) {if (StatusCode.OK == status) {status = StatusCode.NOT_AVAILABLE;}throw new ServiceSpecificException(status, getValueErrorMessage("get", requestedPropValue, result.errorMsg));}return result.propValue;}private ValueResult internalGet(HalPropValue requestedPropValue) {final ValueResult result = new ValueResult();try {result.propValue = mVehicle.get(requestedPropValue);result.status = StatusCode.OK;result.errorMsg = new String();}...return result;}...
}
internalGet() 的实现由持有的 VehicleStub 实例的 get 方法完成,其实现对应于依据 HIDL 的配置调用 HAL 侧获取相应数据。
public abstract class VehicleStub {...@Nullablepublic abstract HalPropValue get(HalPropValue requestedPropValue)throws RemoteException, ServiceSpecificException;...
}
写入
set 写入的链路和 get 大同小异,主要区别是:
- 事先构建待写入的属性实例
CarPropertyValue
并传入 - 传入属性变化时 callback 用的
CarPropertyEventListenerToService
实例
public class CarPropertyManager extends CarManagerBase {public void setIntProperty(int prop, int areaId, int val) {setProperty(Integer.class, prop, areaId, val);}public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {checkSupportedProperty(propId);try {runSyncOperation(() -> {mService.setProperty(new CarPropertyValue<>(propId, areaId, val),mCarPropertyEventToService);return null;});}...}
}
下一层 CarPropertyService
的实现也是通过 PropertyHalService
进行。
传入的 CarPropertyEventListenerToService 其实是 ICarPropertyEventListener AIDL 代理,这里会将其转为 Binder 对象,按照调用的源头 client 缓存起来,在属性变化的时候用。
public class CarPropertyService extends ICarProperty.Stubimplements CarServiceBase, PropertyHalService.PropertyHalListener {public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)throws IllegalArgumentException, ServiceSpecificException {int propId = prop.getPropertyId();...runSyncOperationCheckLimit(() -> {mHal.setProperty(prop);return null;});IBinder listenerBinder = listener.asBinder();synchronized (mLock) {Client client = mClientMap.get(listenerBinder);if (client == null) {client = new Client(listener);}if (client.isDead()) {Slogf.w(TAG, "the ICarPropertyEventListener is already dead");return;}mClientMap.put(listenerBinder, client);updateSetOperationRecorderLocked(propId, prop.getAreaId(), client);}}...
}
继续分发到 VehicleHal
侧。
public class PropertyHalService extends HalServiceBase {public void setProperty(CarPropertyValue prop)throws IllegalArgumentException, ServiceSpecificException {int halPropId = managerToHalPropId(prop.getPropertyId());...HalPropValue halPropValue = mPropValueBuilder.build(prop, halPropId, propConfig);// CarPropertyManager catches and rethrows exception, no need to handle here.mVehicleHal.set(halPropValue);}...
}
后续一样的是通过 VehicleHal
到 HalClient
,再到 VehicleStub
,最后抵达 HAL。
public class VehicleHal implements HalClientCallback {...public void set(HalPropValue propValue)throws IllegalArgumentException, ServiceSpecificException {mHalClient.setValue(propValue);}
}final class HalClient {...public void setValue(HalPropValue propValue)throws IllegalArgumentException, ServiceSpecificException {ObjectWrapper<String> errorMsgWrapper = new ObjectWrapper<>();errorMsgWrapper.object = new String();int status = invokeRetriable(() -> {try {mVehicle.set(propValue);errorMsgWrapper.object = new String();return StatusCode.OK;}...}, mWaitCapMs, mSleepMs);...}...
}public abstract class VehicleStub {...public abstract void set(HalPropValue propValue)throws RemoteException, ServiceSpecificException;...
}
结语
结合一张图回顾下整个过程:
- App 先通过 Car lib 拿到
CarService
的Car
实例,CarService 会初始化所有 Car 相关的实现,比如其中车辆属性的化,会初始化CarPropertyService
和PropertyHalService
等 - 接着,App 会从 Car 实例获取车辆某个接口的实例,比如控制车辆属性的话,需要获取
CarPropertyManager
,CarService 则会从初始化完成的 map 里返回已准备好的对应对象 - App 的属性读写会通过 AIDL 接口抵达直接负责的 CarPropertyService,然后到与 HAL 中车辆属性模块交互的
PropertyHalService
,再到综合的VehicleHal
,最后通过 HIDL 接口抵达以及更下面的Hal
,并按照定义的数据类型更改 ECU 的相关属性
希望本文能言简意赅地带你了解车辆属性的大体全貌,感谢阅读。
推荐阅读
- 一文了解 Android 车机如何处理中控的旋钮输入
- 从实体按键看 Android 车机的自定义事件机制
- 深度入门 Android 车机核心 CarService 的构成和链路
- Android 车机初体验:Auto,Automotive 傻傻分不清楚?
参考资料
- CarPropertyManager
99%的人还看了
相似问题
- SpringBoot使用ObjectMapper之Long和BigDemical类型的属性字符串处理,防止前端丢失数值精度
- 〖大前端 - 基础入门三大核心之JS篇㊲〗- DOM改变元素节点的css样式、HTML属性
- QT中样式表常见属性与颜色的设置与应用
- Java继承中的属性名相同但是类型不同的情况
- C#开发的OpenRA游戏之属性QuantizeFacingsFromSequence(7)
- XmlElement注解在Java的数组属性上,以产生多个相同的XML元素
- CSS-列表属性篇
- CSS 文本属性篇
- 计算属性与watch的区别,fetch与axios在vue中的异步请求,单文本组件使用,使用vite创建vue项目,组件的使用方法
- JAXB:用XmlElement注解复杂类型的Java属性,来产生多层嵌套的xml元素
猜你感兴趣
版权申明
本文"从车窗升降一探 Android 车机的重要 API:车辆属性 CarProperty":http://eshow365.cn/6-21854-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!