【Android R】车载 Android 核心服务 CarPropertyService_carpropertymanager怎么注册信号回调(3)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值