网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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) 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 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 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 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的流程并不复杂,这里就不再粘贴源码了逐个讲解了,贴上一份设定的时序图。
![](https://img-blog.csdnimg.cn/img_convert/b63d4e8c3c08acac97e4a85ff2806316.jpeg)
#### 权限控制
上面在分析CarPropertyService监听属性变化的具体实现时,我们提到了使用Car API的接口需要注册对应的权限,那么这些权限是如何管理的呢?
在PropertyHalService的构造方法中,创建了一个PropertyHalServiceIds的对象,而这个对象就是用来存储每个属性所需要的权限的。
>
> PropertyHalServiceIds源码位置:[/packages/services/Car/service/src/com/android/car/hal/PropertyHalServiceIds.java](https://bbs.csdn.net/topics/618636735)
>
>
>
public PropertyHalService(VehicleHal vehicleHal) {
mPropIds = new PropertyHalServiceIds();
mSubscribedHalPropIds = new HashSet();
mVehicleHal = vehicleHal;
}
在PropertyHalServiceIds的构造方法中,将每个属性对应需要的权限进行了一一关联,保存在一个SparseArray中。
![](https://img-blog.csdnimg.cn/img_convert/de328ce1b8d776a2ce697685d8f4fff3.png)
那么接下来我们以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/](https://bbs.csdn.net/topics/618636735)
>
>
>
#### 源码结构
VehicleHAL主要由**IPC接口**和**交互逻辑**两部分组成,如下所示
![](https://img-blog.csdnimg.cn/img_convert/17f42da2771c3eece5cc3af848b62d83.png)
* IVehicle.hal
定义VehicleHAL对外暴露的方法。
* IVehicleCallback.hal
定义属性变化时的回调接口
* types.hal
定义在`IVehicle.hal`与`IVehicleCallback.hal`中使用的数据结构和属性值。
上述三个hal文件的结构与AIDL的通信结构非常相似,不过这种通信方式叫做HIDL(读作:嗨豆)。
有关HIDL的进一步内容,请参考官方文档:[source.android.google.cn/docs/core/a…](https://bbs.csdn.net/topics/618636735)
* default/ utils/
是Android Automotive OS对于VechicleHAL的参考实现。但是其中并没有实现与车辆总线进行数据交互这样的业务逻辑,这块的内容需要主机制造商自行实现。
#### VehicleHAL 接口
VHAL 支持以下接口:
* getAllPropConfigs() generates (vec propConfigs);
列出 VehicleHAL 所支持的所有属性的配置。CarService 仅使用支持的属性。
* getPropConfigs(vec<int32\_t> 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**
如下图所示:
![](https://img-blog.csdnimg.cn/img_convert/7174bfc96aeeea8cf15c5db4a257b258.png)
由于博主并没有实际从事过HAL层的开发,有关VehicleHAL就只介绍到这里,实际工作中一般会有单独负责HAL层的同事编写这里的代码。
更多内容请参考官方的文档:[source.android.google.cn/docs/device…](https://bbs.csdn.net/topics/618636735)
### 总结
本篇介绍了CarPropertyService的实现原理,但是仅通过阅读这篇文章其实并不能完全掌握整个CarPropertyService,这是任何技术性文章都做不到的通病,实际项目依然需要我们仔细阅读源码,分析方法的含义,本篇文章的实际目的是让你弄清楚关键节点的实现方式和运行原理。
我个人也负责过`CarPropertyService`的开发工作,当时由于对`CarPropertyService`的运行机制并不了解,选择整个重写`CarPropertyService`,现在想想着实走了不少弯路。
好了,感谢你的阅读,希望能帮助到你。
作者:林栩link
原文链接: (https://juejin.cn/post/7167636977548787742)
### 最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
![img](https://img-blog.csdnimg.cn/img_convert/8e239dbf1f0068cbe4a7966249da7472.png)
![img](https://img-blog.csdnimg.cn/img_convert/94c09a17b062d26b73417882e592de89.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
rvice`,现在想想着实走了不少弯路。
好了,感谢你的阅读,希望能帮助到你。
作者:林栩link
原文链接: (https://juejin.cn/post/7167636977548787742)
### 最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
[外链图片转存中...(img-VkpOTsXg-1715907573071)]
[外链图片转存中...(img-1zZ2fILN-1715907573071)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**