深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
类型 | 常量名 |
---|---|
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()方法为例,看一下是如何限制无权限应用的调用的。
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
mg-blog.csdnimg.cn/img_convert/de328ce1b8d776a2ce697685d8f4fff3.png)
那么接下来我们以getProperty()方法为例,看一下是如何限制无权限应用的调用的。
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
[外链图片转存中…(img-noa2QKva-1715907498707)]
[外链图片转存中…(img-iCqr8b7c-1715907498708)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新