Android 15 CarService源码09-CarPropertyService
前言¶
在 Android 15 CarService源码08-CarPropertyService之接口 分析了客户端如何通过 CarPropertyManager 跟 CarPropertyService,接下来我们继续分析 CarPropertyService 的初始化流程。
初始化¶
根据 Android 15 CarService源码02-服务初始化 的分析,CarService 服务的初始化过程实际上包括以下几个步骤:
-
Native服务的初始化:
- 首先,构建每个
HalService实例。 - 然后,调用
HalService的takeProperties()方法。 - 接着,调用
HalService的init()方法。
- 首先,构建每个
-
Java服务的初始化:
- 构建每个
CarSystemService实例。 - 调用
CarSystemService的init()方法。 - 最后,调用
CarSystemService的onInitComplete()方法。
- 构建每个
接下来,我们将按照这个思路,从 PropertyHalService 开始进行分析。
PropertyHalService¶
PropertyHalService构造函数¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
/**
* 该类是一个HAL服务的公共接口类,用于通过ICarProperty来传输车辆属性。
* 所有通过ICarProperty来进行车辆属性传递的服务应该继承该类。
* 该类属于HAL(硬件抽象层)服务的一部分。
*/
public class PropertyHalService extends HalServiceBase {
// 车辆硬件抽象层的接口实例,负责与底层硬件通信
private final VehicleHal mVehicleHal;
// 用于构建Hal属性值的辅助工具类,负责创建和管理车辆属性值
private final HalPropValueBuilder mPropValueBuilder;
/**
* 构造函数,初始化PropertyHalService实例。
*
* @param vehicleHal 车辆硬件抽象层接口的实例,提供与硬件层的交互。
*/
public PropertyHalService(VehicleHal vehicleHal) {
// 初始化 mVehicleHal 为传入的 vehicleHal 实例
mVehicleHal = vehicleHal;
// 如果启用了调试模式,则输出服务启动的日志
if (DBG) {
Slogf.d(TAG, "started PropertyHalService");
}
// 获取用于构建属性值的工具类实例
mPropValueBuilder = vehicleHal.getHalPropValueBuilder();
}
}
PropertyHalService 是一个继承自 HalServiceBase 的服务类,专门用于通过 ICarProperty 接口传输车辆属性。它负责初始化与车辆硬件的通信接口(VehicleHal)并提供构建车辆属性值的工具(HalPropValueBuilder)。
VehicleHal.getHalPropValueBuilder()¶
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
/**
* 车辆硬件抽象层(Vehicle HAL)的抽象类。
* 该类处理与原生 HAL 的接口,并对接收到的数据进行基本的解析(如类型检查)。
* 然后,每个事件会被发送到相应的 {@link HalServiceBase} 实现中。
* {@link HalServiceBase} 负责将数据转换为对应的 Car*Service 供 Car*Manager API 使用。
*/
public class VehicleHal implements VehicleHalCallback, CarSystemService {
// 用于构建车辆属性值的工具类实例
private final HalPropValueBuilder mPropValueBuilder;
/**
* 获取 Hal 属性值构建器的实例。
*
* @return 返回 HalPropValueBuilder 实例
*/
public HalPropValueBuilder getHalPropValueBuilder() {
return mPropValueBuilder;
}
/**
* 构造函数,初始化 VehicleHal 实例。
*
* @param vehicle 车辆实例,用于获取 HAL 属性值构建器
*/
VehicleHal(... VehicleStub vehicle) {
// 必须在 HalService 初始化之前完成初始化,以便 HalService 使用该实例
mPropValueBuilder = vehicle.getHalPropValueBuilder();
...
}
}
VehicleHal 类是一个车辆硬件抽象层(HAL)的实现,它与底层的原生 HAL 进行交互,接收数据并进行基本的解析,然后将解析后的事件交给相应的 HalServiceBase 实现来处理。HalServiceBase 的责任是将数据转换为具体的 Car*Service,供 Car*Manager API 使用。这个类的核心职责是提供与车辆硬件的交互接口,并帮助构建属性值。
AidlVehicleStub.getHalPropValueBuilder()¶
// packages/services/Car/service/src/com/android/car/AidlVehicleStub.java
final class AidlVehicleStub extends VehicleStub {
/**
* 获取一个 HalPropValueBuilder,用于构建 HalPropValue。
*
* @return 返回一个构建 HalPropValue 的构建器实例。
*/
@Override
public HalPropValueBuilder getHalPropValueBuilder() {
return mPropValueBuilder;
}
/**
* AidlVehicleStub 的构造函数,初始化 AidlVehicleStub 实例。
*
* @param aidlVehicle AIDL 接口实例,用于与底层车辆硬件进行交互。
* @param handlerThread 用于处理异步任务的线程。
*/
@VisibleForTesting
AidlVehicleStub(IVehicle aidlVehicle, HandlerThread handlerThread) {
mAidlVehicle = aidlVehicle;
// 使用传入的参数初始化 HalPropValueBuilder,isAidl 标志为 true,表示这是一个 AIDL 实现。
mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true);
...
}
}
AidlVehicleStub 类是 VehicleStub 类的子类,专门用于实现通过 AIDL 进行的车辆硬件交互。它重写了 getHalPropValueBuilder() 方法来返回一个构建 HalPropValue 的构建器实例。构造函数用于初始化与 AIDL 接口交互的相关实例(如 IVehicle)并配置一个用于构建属性值的构建器 (HalPropValueBuilder)。mPropValueBuilder 的初始化时传入 isAidl=true,表明它是 AIDL 特定的实现。
PropertyHalService.takeProperties()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
// 该方法在 HAL 的初始化过程中调用。避免在此处处理复杂的操作。
@Override
public void takeProperties(Collection<HalPropConfig> halPropConfigs) {
// 遍历所有传入的属性配置
for (HalPropConfig halPropConfig : halPropConfigs) {
// 获取当前属性的 ID
int halPropId = halPropConfig.getPropId();
// 检查该属性是否是支持的
if (isSupportedProperty(halPropId)) {
synchronized (mLock) {
// 如果该属性是支持的,存储该属性配置到哈希映射中
mHalPropIdToPropConfig.put(halPropId, halPropConfig);
}
if (DBG) {
// 如果调试模式开启,打印日志,记录已接收并支持的属性
Slogf.d(TAG, "takeSupportedProperties: %s", halPropIdToName(halPropId));
}
} else {
// 如果该属性不被支持,打印日志,记录忽略该属性的情况
if (DBG) {
Slogf.d(TAG, "takeProperties: Property: %s is not supported, ignore",
halPropIdToName(halPropId));
}
}
}
// 如果调试模式开启,打印总共处理的属性数量
if (DBG) {
Slogf.d(TAG, "takeSupportedProperties() took %d properties", halPropConfigs.size());
}
// 检查车辆 HAL 是否支持选择供应商属性的权限
HalPropConfig customizePermission = mVehicleHal.getPropConfig(
VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION);
if (customizePermission != null) {
// 如果支持,定制供应商权限
mPropertyHalServiceConfigs.customizeVendorPermission(
customizePermission.getConfigArray());
} else {
// 如果不支持,打印日志
if (DBG) {
Slogf.d(TAG, "No custom vendor permission defined in VHAL");
}
}
}
}
我们在回顾一下 takeProperties() 是在 VehicleHal.priorityInit() 调用的,在 Android 15 CarService源码02-服务初始化 有分析。
在这个例子中PropertyHalService 中传进来的 halPropConfigs 比较特殊。其大致代码如下:
public class VehicleHal implements VehicleHalCallback, CarSystemService {
public void priorityInit() {
// 获取所有属性配置。
fetchAllPropConfigs();
synchronized (mLock) {
for (int i = 0; i < mAllServices.size(); i++) {
// 获取服务支持的所有属性。
int[] supportedProps = service.getAllSupportedProperties();
if (supportedProps.length == 0) {
...
} else {
// 如果服务有明确支持的属性,只处理这些属性。
for (int prop : supportedProps) {
HalPropConfig config = mAllProperties.get(prop);
if (config == null) {
continue;
}
// 将属性处理程序与服务关联。
mPropertyHandlers.append(prop, service);
// 将属性配置添加到服务的配置列表中。
configsForService.add(config);
}
}
}
}
// 初始化每个服务并传递其属性配置。
for (Map.Entry<HalServiceBase, ArrayList<HalPropConfig>> entry
: configsForAllServices.entrySet()) {
HalServiceBase service = entry.getKey();
ArrayList<HalPropConfig> configsForService = entry.getValue();
// 让服务接管其属性配置。
service.takeProperties(configsForService);
// 初始化服务。
service.init();
}
}
}
因为 service.getAllSupportedProperties() 也就是调用 PropertyHalService.getAllSupportedProperties() 得到是空的,所有是走else流程。那么这里传进来的 takeProperties(Collection<HalPropConfig> halPropConfigs) 其实就是从 IVehicle.getAllPropConfigs() 得到的。
回到 PropertyHalService.takeProperties() 中:
- 检查是否支持该属性:
isSupportedProperty(halPropId),支持的属性会被存储在一个哈希映射(mHalPropIdToPropConfig)中,映射的键是属性 ID,值是该属性的配置。 - 检查是否支持定制的供应商权限:通过调用
mVehicleHal.getPropConfig()获取特定的车辆属性配置,判断车辆 HAL 是否支持定制的供应商权限(SUPPORT_CUSTOMIZE_VENDOR_PERMISSION)。
PropertyHalService.isSupportedProperty()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
// 该字段用于存储 PropertyHalServiceConfigs 实例,该实例包含了与 PropertyHalService 配置相关的信息
private PropertyHalServiceConfigs mPropertyHalServiceConfigs =
PropertyHalServiceConfigs.getInstance();
/**
* 判断指定的车辆属性是否被支持。
*
* @param halPropId 车辆属性的 ID
* @return 如果该属性被支持,返回 true;否则返回 false
*/
@Override
public boolean isSupportedProperty(int halPropId) {
// 首先检查该属性是否在 PropertyHalServiceConfigs 中被支持,
// 其次再检查该属性是否在 CarPropertyHelper 中被支持,最终决定该属性是否被支持
return mPropertyHalServiceConfigs.isSupportedProperty(halPropId)
&& CarPropertyHelper.isSupported(halToManagerPropId(halPropId));
}
}
- 调用
mPropertyHalServiceConfigs实例的isSupportedProperty()方法,检查该属性 ID 是否在配置中被支持。 - 调用
CarPropertyHelper类的isSupported()方法,进一步判断该属性是否被车辆管理层支持。需要注意的是,halToManagerPropId()方法是将 HAL 属性 ID 转换为车辆管理层使用的属性 ID,涉及到不同层次之间的属性映射。
PropertyHalServiceConfigs.isSupportedProperty()¶
// packages/services/Car/service/src/com/android/car/hal/property/PropertyHalServiceConfigs.java
public class PropertyHalServiceConfigs {
/**
* 判断属性 ID 是否在 PropertyHalService 所关心的已知属性列表中。
*
* @param propId 车辆属性的 ID
* @return 如果该属性 ID 是 PropertyHalService 所关心的属性,返回 true;否则返回 false
*/
public boolean isSupportedProperty(int propId) {
// 检查属性 ID 是否存在于 mHalPropIdToCarSvcConfig 映射中,
// 如果存在,表示该属性是 PropertyHalService 所关心的已知属性。
// 或者检查该属性 ID 是否是厂商自定义或回溯的属性。
return mHalPropIdToCarSvcConfig.get(propId) != null
|| CarPropertyHelper.isVendorOrBackportedProperty(propId);
}
}
PropertyHalServiceConfigs 类提供了一种机制来判断 PropertyHalService 是否支持某个特定的车辆属性。它通过检查属性 ID 是否在 CarSvcProps.json 配置中。
这里如果深入去分析,发现 属性是否被支持 涉及到了很多内容,笔者实际上也做过这个模块,就不深入去分析了。 但我们可以预留一个 TODO , 如果后续有需求可以再继续深入。
PropertyHalService.init()¶
这里的 init() 函数不做任何操作。
PropertyHalService.getAllSupportedProperties()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
@Override
public int[] getAllSupportedProperties() {
return EMPTY_INT_ARRAY;
}
}
public final class CommonConstants {
public static final int[] EMPTY_INT_ARRAY = new int[0];
}
在 PropertyHalService.takeProperties() 我们讲过 getAllSupportedProperties() 获取到的是空的。
CarPropertyService¶
CarPropertyService构造函数¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
// 构造函数,使用 Builder 模式构造 CarPropertyService 对象
private CarPropertyService(Builder builder) {
// 如果调试模式开启,打印服务启动的日志信息
if (DBG) {
Slogf.d(TAG, "CarPropertyService started!");
}
// 使用 builder 来初始化成员变量
mPropertyHalService = Objects.requireNonNull(builder.mPropertyHalService); // 必须传入 PropertyHalService 对象
mContext = Objects.requireNonNull(builder.mContext); // 必须传入 Context 对象
// 如果 FeatureFlags 未提供,则使用默认实现 FeatureFlagsImpl
mFeatureFlags = Objects.requireNonNullElseGet(builder.mFeatureFlags,
() -> new FeatureFlagsImpl());
// 如果 HistogramFactory 未提供,则使用默认实现 SystemHistogramFactory
mHistogramFactory = Objects.requireNonNullElseGet(builder.mHistogramFactory,
() -> new SystemHistogramFactory());
// 初始化直方图(Histogram),这通常用于统计服务运行中的性能数据
initializeHistogram();
}
}
CarPropertyService 继承自 ICarProperty.Stub。ICarProperty 是一个用于管理和操作车辆属性(如车辆信息、状态等)的 AIDL 接口。
实现 PropertyHalService.PropertyHalListener 接口,监听来自 PropertyHalService 的回调事件。
其构造函数函数比较简单,就不深入分析了。
CarPropertyService.init()¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
/**
* 初始化方法。
* 该方法在 CarPropertyService 启动时被调用,用于缓存车辆属性配置列表,
* 并设置 PropertyHalService 的回调监听器。
*/
@Override
public void init() {
// 加锁确保线程安全,避免并发问题
synchronized (mLock) {
// 从 PropertyHalService 获取车辆属性列表并缓存
// 目的是避免后续的 binder 调用来获取属性列表,减少性能开销
mPropertyIdToCarPropertyConfig = mPropertyHalService.getPropertyList();
// 如果调试模式开启,打印缓存的车辆属性配置列表的大小
if (DBG) {
Slogf.d(TAG, "cache CarPropertyConfigs " + mPropertyIdToCarPropertyConfig.size());
}
}
// 设置 PropertyHalService 的回调监听器为当前类(this),用于监听来自 PropertyHalService 的事件
mPropertyHalService.setPropertyHalListener(this);
}
}
- 获取车辆属性列表:
mPropertyHalService.getPropertyList()用于从PropertyHalService获取车辆属性的列表。该列表包含了每个车辆属性的配置信息,比如属性 ID 和相关配置。 - 设置回调监听器:调用
mPropertyHalService.setPropertyHalListener(this),将当前的CarPropertyService实例作为监听器(回调)。这样,PropertyHalService在有更新的车辆属性时,可以通知CarPropertyService,从而实现监听车辆属性的变化。
PropertyHalService.getPropertyList()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* 获取所有车辆属性的配置信息。
*
* @return SparseArray<CarPropertyConfig> 车辆属性配置列表,其中每个条目是一个车辆属性 ID 和对应的配置。
*/
public SparseArray<CarPropertyConfig<?>> getPropertyList() {
// 如果调试模式开启,打印日志信息,表明正在调用 getPropertyList 方法
if (DBG) {
Slogf.d(TAG, "getPropertyList");
}
// 使用同步块,确保线程安全,避免并发访问共享资源出现问题
synchronized (mLock) {
// 创建一个 SparseArray 存储转换后的车辆属性配置
SparseArray<CarPropertyConfig<?>> mgrPropIdToCarPropConfig = new SparseArray<>();
// 遍历所有 HAL 属性配置列表(mHalPropIdToPropConfig),将每个配置转换为车辆属性配置
for (int i = 0; i < mHalPropIdToPropConfig.size(); i++) {
// 获取 HAL 属性配置对象
HalPropConfig halPropConfig = mHalPropIdToPropConfig.valueAt(i);
// 将 HAL 属性的属性 ID 转换为管理器级别的属性 ID
int mgrPropId = halToManagerPropId(halPropConfig.getPropId());
// 使用 halPropConfig 对象来生成对应的车辆属性配置对象(CarPropertyConfig)
CarPropertyConfig<?> carPropertyConfig = halPropConfig.toCarPropertyConfig(
mgrPropId, mPropertyHalServiceConfigs);
// 将转换后的车辆属性配置放入到 SparseArray 中,键是管理器级别的属性 ID,值是对应的车辆属性配置
mgrPropIdToCarPropConfig.put(mgrPropId, carPropertyConfig);
}
// 返回车辆属性配置列表
return mgrPropIdToCarPropConfig;
}
}
}
首先,这里的 mHalPropIdToPropConfig 就是前面 PropertyHalService.takeProperties() 存储的所有属性。
getPropertyList() 方法主要作用是从 PropertyHalService 中获取所有的车辆属性配置,转换为适合 Manager 使用的配置格式,并将它们存储在一个 SparseArray 中返回。该方法通过遍历 HAL 属性配置列表,将每个属性配置转换为 Manager 所需的车辆属性配置对象,并以管理器级属性 ID 为键,将配置对象存储在 SparseArray 中,最后返回。为了保证线程安全,方法内部采用了 synchronized 锁。
PropertyHalService.setPropertyHalListener()¶
调用mPropertyHalService.setPropertyHalListener(this),将当前的 CarPropertyService 实例作为监听器(回调)。这样,PropertyHalService 在有更新的车辆属性时,可以通知 CarPropertyService,从而实现监听车辆属性的变化。这里不详细分析,后续在回调章节中再详细分析。
CarPropertyService.onInitComplete()¶
这里的 CarPropertyService 不重写 CarSystemService.onInitComplete() 方法而直接使用默认实现。
属性事件回调¶
在 Android 15 CarService源码04-CarService与Vehicle HAL交互 中,我们知道了 CarService 接收 Vehicle HAL 的回调,主要事件流如下:
IVehicleCallback.onPropertyEvent() // AidlSubscriptionClient
---> AidlVehicleStub.onPropertyEvent()
---> VehicleHal.onPropertyEvent()
---> VehicleHal.handleOnPropertyEvent()
---> PropertyHalService.onHalEvents()
---> CarPropertyService.onPropertyChange()
现在我们就从 CarPropertyService 监听属性事件开始分析,并且从 PropertyHalService.onHalEvents() 倒序分析到 VehicleHal.onPropertyEvent() 。
CarPropertyService 监听属性事件¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
@Override
public void init() {
...
// 设置 PropertyHalService 的回调监听器为当前类(this),用于监听来自 PropertyHalService 的事件
mPropertyHalService.setPropertyHalListener(this);
}
}
PropertyHalService.setPropertyHalListener()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
// 使用锁来保证线程安全,保护 mPropertyHalListener 的访问
@GuardedBy("mLock")
private PropertyHalListener mPropertyHalListener;
/**
* 设置 HAL 服务的监听器
*
* @param propertyHalListener 用于监听属性变化和属性设置错误的监听器
*/
public void setPropertyHalListener(PropertyHalListener propertyHalListener) {
// 使用同步锁来确保线程安全
synchronized (mLock) {
// 设置监听器
mPropertyHalListener = propertyHalListener;
}
}
}
mPropertyHalListener 是一个类型为 PropertyHalListener 的成员变量,用来存储监听器实例。这个监听器主要用于接收车辆属性的变化事件和属性设置错误事件(具体的事件由 onPropertyChange 和 onPropertySetError 方法处理)。
我们看下 PropertyHalListener :
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* PropertyHalListener 接口用于将事件发送给 CarPropertyService
*/
public interface PropertyHalListener {
/**
* 当车辆属性值更新时,调用此方法通知 CarPropertyService
*
* @param events 车辆属性事件列表,包含了所有属性的更新事件
*/
void onPropertyChange(List<CarPropertyEvent> events);
/**
* 当设置车辆属性失败时,调用此方法进行错误通知
*
* @param property 失败的属性 ID,表示哪个属性的设置失败
* @param area 设置失败的区域,可能是车内外不同区域的区分
* @param errorCode 错误码,标识设置失败的原因
* 使用 @CarSetPropertyErrorCode 标注,具体值由车载系统定义
*/
void onPropertySetError(int property, int area,
@CarSetPropertyErrorCode int errorCode);
}
}
onPropertyChange():车辆属性值更新时被调用。events参数是一个List<CarPropertyEvent>类型的列表,包含多个属性事件,每个事件对应一个车辆属性的更新。onPropertySetError():设置车辆属性时发生错误时回调通知。property:表示哪个属性的设置失败,比如设置车速、油量、温度等属性时可能会失败。area:表示失败的区域或作用范围,可能涉及车内外不同区域的控制,例如设置车窗的温度,可能涉及到前后不同区域的设置失败。errorCode:表示错误的具体码,使用@CarSetPropertyErrorCode注解,定义了错误的类型,例如权限不足、设备异常等。具体的错误码由车载系统定义。
PropertyHalService.onHalEvents()¶
现在我们来看 PropertyHalService.onHalEvents() 都做了什么。
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* 处理 HAL 事件,并将其转换为对应的 CarPropertyEvent 事件
* 该方法用于处理从硬件抽象层 (HAL) 收到的属性更新事件。
*
* @param halPropValues 从 HAL 收到的属性值列表
*/
@Override
public void onHalEvents(List<HalPropValue> halPropValues) {
// 用于存储需要分发的 CarPropertyEvent 事件列表
List<CarPropertyEvent> eventsToDispatch = new ArrayList<>();
// 存储成功设置值的结果,这些值是目标值更新所导致的。
Map<VehicleStubCallback, List<GetSetValueResultWrapper>> callbackToSetValueResults =
new ArrayMap<>();
// 对 mLock 加锁,保证线程安全
synchronized (mLock) {
// 遍历从 HAL 收到的所有 HalPropValue 事件
for (HalPropValue halPropValue : halPropValues) {
if (halPropValue == null) {
continue; // 如果事件为空,跳过
}
// 获取该属性的 ID
int halPropId = halPropValue.getPropId();
// 从配置中获取对应的 HalPropConfig
HalPropConfig halPropConfig = mHalPropIdToPropConfig.get(halPropId);
if (halPropConfig == null) {
// 如果没有找到该属性的配置,说明该属性不被支持,忽略此事件
Slogf.w(TAG, "onHalEvents - received HalPropValue for unsupported property: %s",
halPropIdToName(halPropId));
continue;
}
// 如果是 debug 版本,检查传递的 payload 是否有效
if (BuildHelper.isDebuggableBuild()
&& !mPropertyHalServiceConfigs.checkPayload(halPropValue)) {
// 如果 payload 无效,丢弃此事件
Slogf.wtf(TAG,
"Drop event for property: %s because it is failed "
+ "in payload checking.", halPropValue);
}
// 将 HAL 属性 ID 转换为Manager的属性 ID
int mgrPropId = halToManagerPropId(halPropId);
// 如果日志级别为 DBG 且事件状态不是 "AVAILABLE" 状态,则输出日志
if (DBG && halPropValue.getStatus() != VehiclePropertyStatus.AVAILABLE) {
Slogf.d(TAG, "Received event %s with status that is not AVAILABLE",
halPropValue);
}
try {
// 将 HalPropValue 转换为 CarPropertyValue,进行属性值封装
CarPropertyValue<?> carPropertyValue = halPropValue.toCarPropertyValue(
mgrPropId, halPropConfig);
// 创建对应的 CarPropertyEvent 事件
CarPropertyEvent carPropertyEvent = new CarPropertyEvent(
CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyValue);
// 将事件添加到待分发的事件列表中
eventsToDispatch.add(carPropertyEvent);
// 检查是否有挂起的等待更新请求,更新它们
checkPendingWaitForUpdateRequestsLocked(halPropId, carPropertyValue,
callbackToSetValueResults);
} catch (IllegalStateException e) {
// 如果属性值无效,丢弃该事件
Slogf.w(TAG, "Drop event %s that does not have valid value", halPropValue);
continue;
}
}
// 更新异步设置请求的订阅率
updateSubscriptionRateForAsyncSetRequestLocked();
}
// 获取 PropertyHalListener,以便分发事件
PropertyHalListener propertyHalListener;
synchronized (mLock) {
propertyHalListener = mPropertyHalListener;
}
// 如果有监听器,则调用其 onPropertyChange 方法分发事件
if (propertyHalListener != null) {
propertyHalListener.onPropertyChange(eventsToDispatch);
}
// 分发设置值的结果给相应的回调
for (VehicleStubCallback callback : callbackToSetValueResults.keySet()) {
callback.sendSetValueResults(callbackToSetValueResults.get(callback));
}
}
}
eventsToDispatch:用来存储需要分发的CarPropertyEvent事件。- 将 HAL 属性值 (
HalPropValue) 转换为CarPropertyValue,并将其封装为CarPropertyEvent。 CarPropertyEvent表示一个属性的更新事件,并被添加到待分发的事件列表eventsToDispatch中。- 通过
checkPendingWaitForUpdateRequestsLocked()检查并更新任何挂起的等待更新的请求,确保所有的设置操作都得到正确的结果。 - 如果有设置监听器 (
PropertyHalListener),则通过该监听器分发处理好的事件。 - 对于每个
VehicleStubCallback回调,发送相应的设置值结果。
也就是说这里的 mPropertyHalListener 就是前面 CarPropertyService.init() 时调用 mPropertyHalService.setPropertyHalListener(this) 注册进来的。
如果细心的同学会发现,在前面的 Android 15 CarService源码06-CarInputService 分析中我们知道,InputHalService.setInputListener() 方法中调用 VehicleHal.subscribePropertySafe() 方法来订阅属性后,事件回调到 InputHalService.onHalEvents() 方法中。并且是根据传进去的属性ID来关联。
但是我们在 PropertyHalService 中并没有看到主动调用 VehicleHal.subscribePropertySafe() 方法来订阅属性。这是怎么回事呢?我们回顾 Android 15 CarService源码04-CarService与Vehicle HAL交互 中的分析,当新的车辆属性事件发生时调用 VehicleHal.onPropertyEvent() 方法。
根据前面我们提到属性事件从 Vehicle 到 PropertyHalService 的流程,我们先看下其跟 InputHalService 有何不同之处。
VehicleHal.handleOnPropertyEvent()¶
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
/**
* VehicleHal 类是车辆硬件抽象层(HAL)的抽象。该类负责处理与本地 HAL 的接口,并对接收到的数据进行基本解析(类型检查)。
* 每个事件会被发送到相应的 {@link HalServiceBase} 实现,由 {@link HalServiceBase} 负责将数据转换为对应的 Car*Service,
* 以便通过 Car*Manager API 提供给上层应用使用。
*/
public class VehicleHal implements VehicleHalCallback, CarSystemService {
/**
* 处理车辆属性事件。
* @param propValues 包含车辆属性事件的列表。
*/
private void handleOnPropertyEvent(List<HalPropValue> propValues) {
synchronized (mLock) { // 确保线程安全,防止并发修改共享数据。
for (int i = 0; i < propValues.size(); i++) {
HalPropValue v = propValues.get(i); // 获取当前事件。
int propId = v.getPropId(); // 获取事件的属性 ID。
// 根据属性 ID 查找对应的 HalServiceBase 实例。
HalServiceBase service = mPropertyHandlers.get(propId);
if (service == null) {
// 如果找不到对应的服务,记录错误日志并跳过该事件。
Slogf.e(CarLog.TAG_HAL, "handleOnPropertyEvent: HalService not found for %s", v);
continue;
}
// 将事件添加到服务的分发列表中。
service.getDispatchList().add(v);
// 将服务添加到待分发的服务列表中。
mServicesToDispatch.add(service);
// 更新事件日志,用于记录该属性的事件信息。
VehiclePropertyEventInfo info = mEventLog.get(propId);
if (info == null) {
// 如果该属性的事件信息不存在,则创建新的事件信息并存储。
info = new VehiclePropertyEventInfo(v);
mEventLog.put(propId, info);
} else {
// 如果事件信息已存在,则添加新的事件。
info.addNewEvent(v);
}
}
}
// 遍历所有待分发的服务,调用其 onHalEvents 方法处理事件。
for (HalServiceBase s : mServicesToDispatch) {
s.onHalEvents(s.getDispatchList()); // 处理事件。
s.getDispatchList().clear(); // 清空服务的分发列表。
}
// 清空待分发的服务列表。
mServicesToDispatch.clear();
}
}
这里的注释很明白了,我们直接看 mPropertyHandlers ,也就是在 Android 15 CarService源码02-服务初始化 中 VehicleHal.priorityInit() 有解释。
VehicleHal.priorityInit()¶
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
/**
* VehicleHal 类是车辆硬件抽象层(HAL)的抽象。该类处理与本地 HAL 的接口,并对接收到的数据进行基本解析(类型检查)。
* 然后,每个事件被发送到相应的 {@link HalServiceBase} 实现。
* {@link HalServiceBase} 的责任是将数据转换为对应的 Car*Service,以便通过 Car*Manager API 使用。
*/
public class VehicleHal implements VehicleHalCallback, CarSystemService {
/**
* 为 VHAL 配置进行优先初始化。
*/
public void priorityInit() {
// 获取所有属性配置。
fetchAllPropConfigs();
// PropertyHalService 将处理大多数属性,因此需要足够大。
ArrayMap<HalServiceBase, ArrayList<HalPropConfig>> configsForAllServices;
synchronized (mLock) {
// 初始化存储每个服务的属性配置的映射。
configsForAllServices = new ArrayMap<>(mAllServices.size());
for (int i = 0; i < mAllServices.size(); i++) {
// 为每个服务创建一个属性配置列表。
ArrayList<HalPropConfig> configsForService = new ArrayList<>();
HalServiceBase service = mAllServices.get(i);
// 将服务和其属性配置列表添加到映射中。
configsForAllServices.put(service, configsForService);
// 获取服务支持的所有属性。
int[] supportedProps = service.getAllSupportedProperties();
if (supportedProps.length == 0) {
// 如果服务没有明确支持的属性,检查所有属性。
for (int j = 0; j < mAllProperties.size(); j++) {
Integer propId = mAllProperties.keyAt(j);
if (service.isSupportedProperty(propId)) {
HalPropConfig config = mAllProperties.get(propId);
// 将属性处理程序与服务关联。
mPropertyHandlers.append(propId, service);
// 将属性配置添加到服务的配置列表中。
configsForService.add(config);
}
}
} else {
// 如果服务有明确支持的属性,只处理这些属性。
for (int prop : supportedProps) {
HalPropConfig config = mAllProperties.get(prop);
if (config == null) {
continue;
}
// 将属性处理程序与服务关联。
mPropertyHandlers.append(prop, service);
// 将属性配置添加到服务的配置列表中。
configsForService.add(config);
}
}
}
}
// 初始化每个服务并传递其属性配置。
for (Map.Entry<HalServiceBase, ArrayList<HalPropConfig>> entry
: configsForAllServices.entrySet()) {
HalServiceBase service = entry.getKey();
ArrayList<HalPropConfig> configsForService = entry.getValue();
// 让服务接管其属性配置。
service.takeProperties(configsForService);
// 初始化服务。
service.init();
}
}
}
这里的注释也很明白了,而且我们之前在 PropertyHalService.takeProperties() 也提到过 getAllSupportedProperties() 获取到的是空。使用这里走到 if (supportedProps.length == 0) 流程里,也就是所有支持的属性都跟 CarPropertyService 关联起来。
再回到 VehicleHal.handleOnPropertyEvent() 中,就很清晰了。也就是说只要是支持的属性有变化,最终都会回调到 PropertyHalService.onHalEvents() 里。
PropertyHalService.onPropertySetError() 跟 PropertyHalService.onHalEvents() 的流程是类似的,我们就不再详细分析其过长了。调用链路为:
VehicleHal.onPropertySetError()
---> VehicleHal.handleOnPropertySetError()
---> HalServiceBase.onPropertySetError()
---> PropertyHalService.onPropertySetError()
CarPropertyService.onPropertyChange()¶
前面提到了在 CarPropertyService.init() 时调用 mPropertyHalService.setPropertyHalListener(this) ,所以事件最终会回调到 CarPropertyService.onPropertyChange() 中。
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
/**
* 处理从 PropertyHalService 收到的属性变化事件,并将事件分发到相应的客户端。
*
* @param events 从 PropertyHalService 接收到的属性变化事件列表
*/
@Override
public void onPropertyChange(List<CarPropertyEvent> events) {
// 用于存储将要分发到各个客户端的事件
Map<CarPropertyServiceClient, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>();
// 对 mLock 加锁,确保线程安全
synchronized (mLock) {
// 遍历所有的属性变化事件
for (int i = 0; i < events.size(); i++) {
CarPropertyEvent event = events.get(i);
// 获取事件中的属性 ID 和区域 ID
int propId = event.getCarPropertyValue().getPropertyId();
int areaId = event.getCarPropertyValue().getAreaId();
// 获取所有订阅了该属性和区域的客户端
Set<CarPropertyServiceClient> clients = mSubscriptionManager.getClients(
propId, areaId);
// 如果没有客户端订阅该属性,输出错误日志并跳过此事件
if (clients == null) {
Slogf.e(TAG,
"onPropertyChange: no listener registered for propId=%s, areaId=%d",
VehiclePropertyIds.toString(propId), areaId);
continue;
}
// 遍历所有订阅该属性的客户端,将事件添加到该客户端的事件列表中
for (CarPropertyServiceClient client : clients) {
List<CarPropertyEvent> eventsForClient = eventsToDispatch.get(client);
if (eventsForClient == null) {
// 如果该客户端还没有事件列表,创建一个新的列表
eventsToDispatch.put(client, new ArrayList<CarPropertyEvent>());
}
// 将当前事件添加到该客户端的事件列表中
eventsToDispatch.get(client).add(event);
}
}
}
// 释放锁后,开始将事件分发给各个客户端
for (CarPropertyServiceClient client : eventsToDispatch.keySet()) {
try {
// 调用客户端的 onEvent 方法,将事件发送给客户端
client.onEvent(eventsToDispatch.get(client));
} catch (RemoteException ex) {
// 如果在调用过程中发生异常(例如连接中断),记录错误日志
Slogf.e(TAG, "onEvent calling failed: " + ex);
}
}
}
}
主要功能是将这些变化事件传递给所有订阅了相关属性的客户端。
eventsToDispatch是一个映射表,用于存储将要发送给各个客户端的事件。为什么要用这个而不是直接查询到就发过客户端的原因是用SubscriptionManager.getClients()查询必须加锁,但是有不想调用客户端onEvent()方法时被阻塞,所以先查询到所有的客户端,再发送。- 调用
SubscriptionManager.getClients()查询客户端,根据propId和areaId查询哪些客户端订阅了该属性。 - 将事件添加到对应客户端的事件列表
eventsToDispatch中。 - 释放锁后分发事件,调用
CarPropertyServiceClient的onEvent方法,将事件发送给客户端。
从这里我们也知道了,CarPropertyService 接收到 PropertyHalService 的回调事件后,最终是会通过 binder 把事件回调给客户端。所以接下来我们继续从分析客户端是如何注册的。
CarPropertyManager 监听属性事件¶
在前面的 Android 15 CarService源码08-CarPropertyService之接口 分析中,我们知道客户端通过调用 CarPropertyManager.subscribePropertyEvents() 监听属性事件的变化,其最终是调到了 CarPropertyService.registerListener() 中。
CarPropertyService.registerListener()¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
/**
* 注册一个监听器,用于监听指定的车辆属性变化事件。
*
* @param carSubscriptions 需要订阅的属性列表
* @param carPropertyEventListener 监听属性变化事件的回调接口
* @throws IllegalArgumentException 如果传入的参数无效
* @throws ServiceSpecificException 如果在服务中发生特定的错误
*/
@Override
public void registerListener(List<CarSubscription> carSubscriptions,
ICarPropertyEventListener carPropertyEventListener)
throws IllegalArgumentException, ServiceSpecificException {
// 确保传入的 carSubscriptions 和 carPropertyEventListener 非空
requireNonNull(carSubscriptions);
requireNonNull(carPropertyEventListener);
// 验证并清理订阅选项,确保所有订阅的属性设置合法
List<CarSubscription> sanitizedOptions =
validateAndSanitizeSubscriptions(carSubscriptions);
CarPropertyServiceClient finalClient;
// 加锁,确保线程安全
synchronized (mLock) {
// 创建客户端,确保在注册时 Binder 没有死亡
CarPropertyServiceClient client = getOrCreateClientForBinderLocked(
carPropertyEventListener);
// 如果客户端为 null,表示客户端已经死亡,不进行订阅操作
if (client == null) {
return;
}
// 遍历 sanitizedOptions,记录更新频率,并打印调试信息
for (int i = 0; i < sanitizedOptions.size(); i++) {
CarSubscription option = sanitizedOptions.get(i);
mSubscriptionUpdateRateHistogram.logSample(option.updateRateHz);
if (DBG) {
Slogf.d(TAG, "registerListener after update rate sanitization, options: "
+ sanitizedOptions.get(i));
}
}
// 将新的订阅选项存储到暂存区,这不会影响当前的订阅状态
mSubscriptionManager.stageNewOptions(client, sanitizedOptions);
// 尝试应用暂存的订阅更改
try {
applyStagedChangesLocked();
} catch (Exception e) {
// 如果应用更改失败,丢弃暂存的更改
mSubscriptionManager.dropCommit();
throw e;
}
// 提交更改,并将客户端添加到订阅列表中
mSubscriptionManager.commit();
for (int i = 0; i < sanitizedOptions.size(); i++) {
CarSubscription option = sanitizedOptions.get(i);
// 根据订阅选项的更新频率,添加不同类型的属性
if (option.updateRateHz != 0) {
// 如果更新频率非零,则为连续属性
client.addContinuousProperty(
option.propertyId, option.areaIds, option.updateRateHz,
option.enableVariableUpdateRate, option.resolution);
} else {
// 如果更新频率为零,则为变化属性
client.addOnChangeProperty(option.propertyId, option.areaIds);
}
}
// 将最终创建的客户端赋值给 finalClient
finalClient = client;
}
// 在后台线程中,获取并分发属性的初始化值
mHandler.post(() ->
getAndDispatchPropertyInitValue(sanitizedOptions, finalClient));
}
}
getOrCreateClientForBinderLocked()创建客户端,这个比较简单,就不再深入分析了。SubscriptionManager.stageNewOptions()存储新的订阅选项,这个会重点分析。applyStagedChangesLocked()尝试应用之前存储的订阅更改,如果失败则调用SubscriptionManager.commit()丢弃这些暂存的更改。这个会重点分析。SubscriptionManager.commit()提交所有的订阅更改,实际将订阅选项应用到系统中,这个会重点分析。getAndDispatchPropertyInitValue()后台线程中获取并分发初始化值。
SubscriptionManager¶
// packages/services/Car/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
/**
* 该类管理 [{propertyId, areaId} -> RateInfoForClients] 映射,并维护两种状态:
* 当前状态和暂存状态。暂存状态表示提议的更改。在将更改应用到底层系统后,调用者可以
* 使用 {@link #commit} 方法将当前状态替换为暂存状态,或者使用 {@link #dropCommit}
* 丢弃暂存状态。
*
* 一个常见的使用模式是:
*
* synchronized (mLock) {
* mSubscriptionManager.stageNewOptions(...);
* // 可以选择暂存其他选项。
* mSubscriptionManager.stageNewOptions(...);
* // 可以选择暂存取消注册。
* mSubscriptionManager.stageUnregister(...);
*
* mSubscriptionManager.diffBetweenCurrentAndStage(...);
* try {
* // 应用差异。
* } catch (Exception e) {
* mSubscriptionManager.dropCommit();
* throw e;
* }
* mSubscriptionManager.commit();
* }
*
* 该类本身不是线程安全的。
*
* @param <ClientType> 客户端类的表示。
*/
public final class SubscriptionManager<ClientType> {
/**
* 用于表示单个客户端的更新率、可变更新率标志和分辨率的类。
*/
private static final class RateInfo {
public final float updateRateHz; // 更新频率(Hz)
public final boolean enableVariableUpdateRate; // 是否启用可变更新率
public final float resolution; // 分辨率
// 构造函数
RateInfo(float updateRateHz, boolean enableVariableUpdateRate, float resolution) {
this.updateRateHz = updateRateHz;
this.enableVariableUpdateRate = enableVariableUpdateRate;
this.resolution = resolution;
}
...
}
/**
* 该类为每个 {propertyId, areaId} 键提供一个抽象,用于存储所有客户端的订阅信息。
* 包含各个客户端的更新频率、分辨率等信息。
*/
private static final class RateInfoForClients<ClientType> {
private final ArrayMap<ClientType, RateInfo> mRateInfoByClient; // 存储每个客户端的更新信息
private final TreeSet<Float> mUpdateRatesHz; // 有序集合,存储所有更新频率
private final ArrayMap<Float, Integer> mClientCountByUpdateRateHz; // 存储每个更新频率对应的客户端数
private final TreeSet<Float> mResolutions; // 有序集合,存储所有分辨率
private final ArrayMap<Float, Integer> mClientCountByResolution; // 存储每个分辨率对应的客户端数
private int mEnableVariableUpdateRateCount; // 启用可变更新率的客户端数
// 构造函数,初始化各种数据结构
RateInfoForClients() {
mRateInfoByClient = new ArrayMap<>();
mUpdateRatesHz = new TreeSet<>();
mClientCountByUpdateRateHz = new ArrayMap<>();
mResolutions = new TreeSet<>();
mClientCountByResolution = new ArrayMap<>();
}
// 拷贝构造函数,创建一个副本
RateInfoForClients(RateInfoForClients other) {
mRateInfoByClient = new ArrayMap<>(other.mRateInfoByClient);
mUpdateRatesHz = new TreeSet<>(other.mUpdateRatesHz);
mClientCountByUpdateRateHz = new ArrayMap<>(other.mClientCountByUpdateRateHz);
mResolutions = new TreeSet<>(other.mResolutions);
mClientCountByResolution = new ArrayMap<>(other.mClientCountByResolution);
mEnableVariableUpdateRateCount = other.mEnableVariableUpdateRateCount;
}
/**
* 获取该 {propertyId, areaId} 对应的最大更新频率。
*/
private float getMaxUpdateRateHz() {
return mUpdateRatesHz.last();
}
/**
* 获取该 {propertyId, areaId} 对应的最小所需分辨率。
*/
private float getMinRequiredResolution() {
return mResolutions.first();
}
// 判断是否所有客户端都启用了可变更新率
private boolean isVariableUpdateRateEnabledForAllClients() {
return mEnableVariableUpdateRateCount == mRateInfoByClient.size();
}
/**
* 获取所有客户端的合并更新信息。
* 使用最大更新频率,最小分辨率,并且只有当所有客户端都启用可变更新率时才启用该功能。
*/
RateInfo getCombinedRateInfo() {
return new RateInfo(getMaxUpdateRateHz(), isVariableUpdateRateEnabledForAllClients(),
getMinRequiredResolution());
}
// 获取所有客户端集合
Set<ClientType> getClients() {
return mRateInfoByClient.keySet();
}
// 获取某个客户端的更新频率
float getUpdateRateHz(ClientType client) {
return mRateInfoByClient.get(client).updateRateHz;
}
// 获取某个客户端是否启用了可变更新率
boolean isVariableUpdateRateEnabled(ClientType client) {
return mRateInfoByClient.get(client).enableVariableUpdateRate;
}
// 获取某个客户端的分辨率
float getResolution(ClientType client) {
return mRateInfoByClient.get(client).resolution;
}
/**
* 添加一个客户端的订阅信息。
*/
void add(ClientType client, float updateRateHz, boolean enableVariableUpdateRate,
float resolution) {
// 如果客户端已存在,先移除旧的订阅信息
remove(client);
// 将新的订阅信息存入
mRateInfoByClient.put(client,
new RateInfo(updateRateHz, enableVariableUpdateRate, resolution));
// 如果启用了可变更新率,增加计数
if (enableVariableUpdateRate) {
mEnableVariableUpdateRateCount++;
}
// 更新更新频率的相关数据
if (!mClientCountByUpdateRateHz.containsKey(updateRateHz)) {
mUpdateRatesHz.add(updateRateHz);
mClientCountByUpdateRateHz.put(updateRateHz, 1);
} else {
mClientCountByUpdateRateHz.put(updateRateHz,
mClientCountByUpdateRateHz.get(updateRateHz) + 1);
}
// 更新分辨率的相关数据
if (!mClientCountByResolution.containsKey(resolution)) {
mResolutions.add(resolution);
mClientCountByResolution.put(resolution, 1);
} else {
mClientCountByResolution.put(resolution,
mClientCountByResolution.get(resolution) + 1);
}
}
/**
* 移除一个客户端的订阅信息。
*/
void remove(ClientType client) {
if (!mRateInfoByClient.containsKey(client)) {
return;
}
RateInfo rateInfo = mRateInfoByClient.get(client);
// 如果该客户端启用了可变更新率,减少计数
if (rateInfo.enableVariableUpdateRate) {
mEnableVariableUpdateRateCount--;
}
// 更新更新频率的相关数据
float updateRateHz = rateInfo.updateRateHz;
if (mClientCountByUpdateRateHz.containsKey(updateRateHz)) {
int newCount = mClientCountByUpdateRateHz.get(updateRateHz) - 1;
if (newCount == 0) {
mClientCountByUpdateRateHz.remove(updateRateHz);
mUpdateRatesHz.remove(updateRateHz);
} else {
mClientCountByUpdateRateHz.put(updateRateHz, newCount);
}
}
// 更新分辨率的相关数据
float resolution = rateInfo.resolution;
if (mClientCountByResolution.containsKey(resolution)) {
int newCount = mClientCountByResolution.get(resolution) - 1;
if (newCount == 0) {
mClientCountByResolution.remove(resolution);
mResolutions.remove(resolution);
} else {
mClientCountByResolution.put(resolution, newCount);
}
}
// 移除该客户端的订阅信息
mRateInfoByClient.remove(client);
}
/**
* 判断是否没有任何客户端订阅。
*/
boolean isEmpty() {
return mRateInfoByClient.isEmpty();
}
}
// 当前订阅状态:存储每个 {propertyId, areaId} 对应的所有客户端信息
PairSparseArray<RateInfoForClients<ClientType>> mCurrentRateInfoByClientByPropIdAreaId =
new PairSparseArray<>();
// 暂存订阅状态:存储待应用的更改
PairSparseArray<RateInfoForClients<ClientType>> mStagedRateInfoByClientByPropIdAreaId =
new PairSparseArray<>();
// 暂存的受影响的属性 ID 和区域 ID
ArraySet<int[]> mStagedAffectedPropIdAreaIds = new ArraySet<>();
}
SubscriptionManager 类用于管理订阅系统的两个状态:当前状态和暂存状态。通过提供的 add 和 remove 方法,管理每个客户端对指定 {propertyId, areaId} 对应的订阅信息,同时支持暂存变更、计算合并后的更新信息、以及提交或丢弃暂存的操作。
RateInfo类:用于表示单个客户端的订阅信息,包括更新频率 (updateRateHz)、是否启用可变更新率 (enableVariableUpdateRate)、以及分辨率 (resolution)。RateInfoForClients类:用于存储某个{propertyId, areaId}对应的所有客户端的订阅信息。该类通过mRateInfoByClient存储每个客户端的订阅信息,此外,还维护了更新频率和分辨率的有序集合 (TreeSet) 和ArrayMap来高效地管理更新频率和分辨率。
SubscriptionManager.stageNewOptions()¶
// packages/services/Car/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
/**
* 准备新的订阅选项。
*
* 这个方法将新的订阅选项应用到暂存区,但不会立即提交它们。
* 客户端应该调用 {@link #diffBetweenCurrentAndStage} 获取当前状态和暂存状态之间的差异。
* 然后将差异应用到底层系统,在操作成功后提交更改,或者在操作失败时丢弃更改。
*
* @param client 客户端对象
* @param options 新的订阅选项列表
*/
public void stageNewOptions(ClientType client, List<CarSubscription> options) {
// 调试模式下打印日志,显示即将应用的订阅选项
if (DBG) {
Slog.d(TAG, "stageNewOptions: options: " + options);
}
// 如果当前状态是干净的(即没有待提交的更改),则克隆当前状态到暂存区
cloneCurrentToStageIfClean();
// 遍历每一个订阅选项
for (int i = 0; i < options.size(); i++) {
CarSubscription option = options.get(i);
int propertyId = option.propertyId; // 获取订阅的属性 ID
// 遍历每个区域 ID
for (int areaId : option.areaIds) {
// 将该 {propertyId, areaId} 对应的订阅信息添加到暂存区
mStagedAffectedPropIdAreaIds.add(new int[]{propertyId, areaId});
// 检查该 {propertyId, areaId} 是否已经在暂存区中存在,如果不存在则创建新的 `RateInfoForClients` 对象
if (mStagedRateInfoByClientByPropIdAreaId.get(propertyId, areaId) == null) {
mStagedRateInfoByClientByPropIdAreaId.put(propertyId, areaId,
new RateInfoForClients<>());
}
// 将当前客户端的订阅信息添加到暂存区的相应位置
mStagedRateInfoByClientByPropIdAreaId.get(propertyId, areaId).add(
client, option.updateRateHz, option.enableVariableUpdateRate,
option.resolution);
}
}
}
stageNewOptions 的目的是准备新的订阅选项,将它们放入“暂存区”,但并不直接提交。这样,客户端可以检查当前订阅状态和暂存状态之间的差异,确认是否成功后再决定提交(commit)或者丢弃(dropCommit)这些变更。
- client:表示正在提交订阅请求的客户端对象,也就是 CarPropertyServiceClient 。
- options:一个 CarSubscription 对象的列表,包含了客户端希望订阅的属性信息。每个 CarSubscription 对象中包括:
- - propertyId:属性的 ID。
- areaIds:该属性所在的区域 ID 列表。
- updateRateHz:更新频率(Hz)。
- enableVariableUpdateRate:是否启用可变更新率。
- resolution:订阅的分辨率。
CarPropertyService.applyStagedChangesLocked()¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
/**
* 应用暂存的订阅变更。
*
* 这个方法在同步锁保护下应用暂存区中待处理的订阅更改。
* 它首先通过比较当前状态与暂存区状态之间的差异,来确定哪些订阅需要被添加、修改或移除。
* 然后,它会将变化应用到底层的 `PropertyHalService` 中,以更新车辆属性服务的订阅状态。
*
* @throws ServiceSpecificException 如果在操作过程中发生服务特定的异常,会抛出此异常
*/
@GuardedBy("mLock")
void applyStagedChangesLocked() throws ServiceSpecificException {
// 创建两个列表来存储需要处理的订阅信息
List<CarSubscription> filteredSubscriptions = new ArrayList<>(); // 存储需要订阅的属性
List<Integer> propertyIdsToUnsubscribe = new ArrayList<>(); // 存储需要取消订阅的属性 ID
// 获取当前和暂存区之间的差异,分别填充 `filteredSubscriptions` 和 `propertyIdsToUnsubscribe` 两个列表
mSubscriptionManager.diffBetweenCurrentAndStage(/* out */ filteredSubscriptions,
/* out */ propertyIdsToUnsubscribe);
// 在调试模式下,打印经过过滤的订阅信息
if (DBG) {
Slogf.d(TAG, "Subscriptions after filtering out options that are already"
+ " subscribed at the same or a higher rate: " + filteredSubscriptions);
}
// 如果需要订阅的属性列表不为空,则执行订阅操作
if (!filteredSubscriptions.isEmpty()) {
try {
// 调用 PropertyHalService 的 subscribeProperty 方法,实际进行订阅
mPropertyHalService.subscribeProperty(filteredSubscriptions);
} catch (ServiceSpecificException e) {
// 如果订阅失败,打印错误日志并抛出异常
Slogf.e(TAG, "PropertyHalService.subscribeProperty failed", e);
throw e;
}
}
// 遍历需要取消订阅的属性 ID 列表
for (int i = 0; i < propertyIdsToUnsubscribe.size(); i++) {
// 打印取消订阅的属性信息
Slogf.d(TAG, "Property: %s is no longer subscribed", propertyIdsToUnsubscribe.get(i));
try {
// 调用 PropertyHalService 的 unsubscribeProperty 方法,实际进行取消订阅
mPropertyHalService.unsubscribeProperty(propertyIdsToUnsubscribe.get(i));
} catch (ServiceSpecificException e) {
// 如果取消订阅失败,打印错误日志并抛出异常
Slogf.e(TAG, "failed to call PropertyHalService.unsubscribeProperty", e);
throw e;
}
}
}
applyStagedChangesLocked() 方法的主要目的是在底层 PropertyHalService 中应用暂存的订阅变更。具体来说,它会根据当前订阅状态与暂存区之间的差异,执行以下操作:
- 订阅新的属性,即那些暂存区中有变更且尚未订阅的属性。
- 取消订阅属性,即那些当前已订阅,但暂存区中已不再需要订阅的属性。
具体步骤如下:
- 获取订阅差异:
SubscriptionManager.diffBetweenCurrentAndStage()方法用于计算当前订阅状态和暂存区之间的差异。具体来说,它会填充两个列表:filteredSubscriptions:包含所有需要新增或更新订阅的属性。propertyIdsToUnsubscribe:包含所有需要取消订阅的属性 ID。
- 订阅操作:如果
filteredSubscriptions列表不为空,表示有属性需要订阅。调用PropertyHalService.subscribeProperty()方法将这些属性提交给PropertyHalService进行订阅。 - 取消订阅操作:遍历
propertyIdsToUnsubscribe列表,逐个取消这些属性的订阅。通过调用PropertyHalService.unsubscribeProperty()方法来取消订阅。
看到这里就迷惑了,因为前面分析
PropertyHalService.onHalEvents()我们提到过在PropertyHalService中并没有看到主动调用VehicleHal.subscribePropertySafe()方法来订阅属性。而是所有支持的属性有变化,默认都会回调到PropertyHalService.onHalEvents()里。那这里又调用PropertyHalService.subscribeProperty()方法进行订阅。这到底是怎么回事呢?
SubscriptionManager.diffBetweenCurrentAndStage()¶
// packages/services/Car/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
/**
* 计算暂存区和当前状态之间的差异。
*
* 该方法通过比较当前状态和暂存区的差异,确定哪些订阅需要添加(新订阅或更新订阅),
* 哪些订阅需要取消(不再订阅的属性)。结果会通过输出参数返回。
*
* @param outDiffSubscriptions 输出参数,包含所有发生变化的订阅,既包括新的订阅,也包括更新了更新频率的订阅。
* @param outPropertyIdsToUnsubscribe 输出参数,包含所有需要取消订阅的属性 ID。
*/
public void diffBetweenCurrentAndStage(List<CarSubscription> outDiffSubscriptions,
List<Integer> outPropertyIdsToUnsubscribe) {
// 如果暂存区没有任何变化,直接返回,避免进行不必要的计算
if (mStagedAffectedPropIdAreaIds.isEmpty()) {
if (DBG) {
Slog.d(TAG, "No changes has been staged, no diff");
}
return;
}
// 记录可能需要取消订阅的属性 ID
ArraySet<Integer> possiblePropIdsToUnsubscribe = new ArraySet<>();
// 存储当前差异的属性和更新信息
PairSparseArray<RateInfo> diffRateInfoByPropIdAreaId = new PairSparseArray<>();
// 遍历所有暂存区中受影响的属性 ID 和区域 ID
for (int i = 0; i < mStagedAffectedPropIdAreaIds.size(); i++) {
int[] propIdAreaId = mStagedAffectedPropIdAreaIds.valueAt(i);
int propertyId = propIdAreaId[0];
int areaId = propIdAreaId[1];
// 如果暂存区中没有该属性和区域的订阅,表示该属性不再被订阅
if (!mStagedRateInfoByClientByPropIdAreaId.contains(propertyId, areaId)) {
// 在调试模式下,打印不再订阅的属性信息
if (DBG) {
Slog.d(TAG, String.format("The property: %s, areaId: %d is no longer "
+ "subscribed", VehiclePropertyIds.toString(propertyId), areaId));
}
// 将该属性的 ID 添加到取消订阅的列表中
possiblePropIdsToUnsubscribe.add(propertyId);
continue;
}
// 获取暂存区中该属性和区域的新的订阅信息
RateInfo newCombinedRateInfo = mStagedRateInfoByClientByPropIdAreaId
.get(propertyId, areaId).getCombinedRateInfo();
// 比较当前状态与暂存状态,如果发生了变化,记录新的订阅信息
if (!mCurrentRateInfoByClientByPropIdAreaId.contains(propertyId, areaId)
|| !(mCurrentRateInfoByClientByPropIdAreaId
.get(propertyId, areaId).getCombinedRateInfo()
.equals(newCombinedRateInfo))) {
// 在调试模式下,打印新的订阅信息
if (DBG) {
Slog.d(TAG, String.format(
"New combined subscription rate info for property: %s, areaId: %d, %s",
VehiclePropertyIds.toString(propertyId), areaId, newCombinedRateInfo));
}
// 记录该属性的订阅信息变化
diffRateInfoByPropIdAreaId.put(propertyId, areaId, newCombinedRateInfo);
continue;
}
}
// 将计算出的差异转换为实际的订阅信息,添加到输出参数 `outDiffSubscriptions` 中
outDiffSubscriptions.addAll(getCarSubscription(diffRateInfoByPropIdAreaId));
// 遍历可能需要取消订阅的属性 ID,检查是否所有区域都已取消订阅
for (int i = 0; i < possiblePropIdsToUnsubscribe.size(); i++) {
int possiblePropIdToUnsubscribe = possiblePropIdsToUnsubscribe.valueAt(i);
// 如果该属性的所有区域都不再订阅,则可以取消该属性的订阅
if (mStagedRateInfoByClientByPropIdAreaId.getSecondKeysForFirstKey(
possiblePropIdToUnsubscribe).isEmpty()) {
// 在调试模式下,打印需要取消订阅的属性信息
if (DBG) {
Slog.d(TAG, String.format(
"All areas for the property: %s are no longer subscribed, "
+ "unsubscribe it", VehiclePropertyIds.toString(
possiblePropIdToUnsubscribe)));
}
// 将该属性 ID 添加到需要取消订阅的列表中
outPropertyIdsToUnsubscribe.add(possiblePropIdToUnsubscribe);
}
}
}
diffBetweenCurrentAndStage() 方法通过比较当前订阅状态(mCurrentRateInfoByClientByPropIdAreaId)和暂存状态(mStagedRateInfoByClientByPropIdAreaId)的差异,来生成变更列表。
- outDiffSubscriptions:输出参数,包含需要新增或更新的订阅信息。
- outPropertyIdsToUnsubscribe:输出参数,包含需要取消订阅的属性 ID。
PropertyHalService.subscribeProperty()¶
回到 CarPropertyService.applyStagedChangesLocked() 中,如果 filteredSubscriptions 列表不为空,表示有属性需要订阅。调用 PropertyHalService.subscribeProperty() 方法将这些属性提交给 PropertyHalService 进行订阅。
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* 订阅指定属性,并设置更新频率(以 Hz 为单位)和区域 ID。
*
* @param carSubscriptions 包含需要订阅的车辆属性订阅列表。该列表不能为空,已经在 CarPropertyService 中进行了检查。
* @throws ServiceSpecificException 如果 VHAL 返回错误,将抛出该异常。
*/
public void subscribeProperty(List<CarSubscription> carSubscriptions)
throws ServiceSpecificException {
// 锁定共享资源,保证线程安全,确保在执行此操作时订阅管理器的状态与 VHAL 中的状态一致。
synchronized (mLock) {
// 遍历所有传入的订阅项,逐个处理
for (int i = 0; i < carSubscriptions.size(); i++) {
// 获取每个订阅项
CarSubscription carSubscription = carSubscriptions.get(i);
// 获取订阅的属性 ID int mgrPropId = carSubscription.propertyId;
// 获取对应的区域 ID int[] areaIds = carSubscription.areaIds;
// 获取更新频率(单位:Hz)
float updateRateHz = carSubscription.updateRateHz;
// 如果开启了调试日志输出,则打印相关信息
if (DBG) {
Slogf.d(TAG, "subscribeProperty propertyId: %s, updateRateHz=%f",
VehiclePropertyIds.toString(mgrPropId), updateRateHz);
}
// 将 manager 中的属性 ID 转换为 HAL 层使用的属性 ID int halPropId = managerToHalPropId(mgrPropId);
// 需要注意的是,实际在订阅管理器中使用的是 halPropId,而不是 mgrPropId // 使用 halPropId 进行订阅请求
mSubManager.stageNewOptions(new ClientType(CAR_PROP_SVC_REQUEST_ID),
List.of(newCarSubscription(halPropId, areaIds, updateRateHz,
carSubscription.enableVariableUpdateRate, carSubscription.resolution)));
}
// 尝试更新订阅频率
try {
updateSubscriptionRateLocked();
} catch (ServiceSpecificException e) {
// 如果更新订阅频率失败,记录错误并抛出异常
Slogf.e(TAG, "Failed to update subscription rate for subscribe", e);
throw e;
}
}
}
}
在这里又看到再次调用 SubscriptionManager.stageNewOptions() 了,在前面的 CarPropertyService.registerListener() 里就调用一次了,但是这里有一个很明显的区别是给了 假的请求 ID CAR_PROP_SVC_REQUEST_ID ,主要的作用是帮助区分 车属性服务的订阅 和 其他异步请求的订阅。
但是这样也有疑问:那什么是 车属性服务的订阅 和 其他异步请求的订阅 ?
PropertyHalService.updateSubscriptionRateLocked()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* 将 {@code mSubManager} 中的暂存订阅更新率应用到 VHAL。
*
* 使用 {@code subscribeProperty} 更新订阅率,或者如果不再订阅,则使用 {@code unsubscribeProperty} 取消订阅。
*
* 这个函数涉及到 VHAL 的 Binder 调用,但我们有意将其保持在锁内部,因为我们需要保持订阅状态的一致性。
* 如果不在这里使用锁,可能会发生以下情况:
*
* <ol>
* <li>线程1获取锁。</li>
* <li>线程1将 mSubManager 更新为状态1。</li>
* <li>根据状态1计算新的更新频率(局部变量)。</li>
* <li>线程1释放锁。</li>
* <li>线程2获取锁。</li>
* <li>线程2将 mSubManager 更新为状态2。</li>
* <li>根据状态2计算新的更新频率(局部变量)。</li>
* <li>线程2释放锁。</li>
* <li>线程2基于状态2调用 subscribeProperty 到 VHAL。</li>
* <li>线程1基于状态1调用 subscribeProperty 到 VHAL。</li>
* <li>现在内部状态是状态2,但 VHAL 中是状态1。</li>
* </ol>
*/
@GuardedBy("mLock")
private void updateSubscriptionRateLocked() throws ServiceSpecificException {
// 用于存储差异化的订阅选项
ArrayList<CarSubscription> diffSubscribeOptions = new ArrayList<>();
// 用于存储需要取消订阅的属性 ID
List<Integer> propIdsToUnsubscribe = new ArrayList<>();
// 获取当前状态与暂存状态之间的差异
mSubManager.diffBetweenCurrentAndStage(diffSubscribeOptions, propIdsToUnsubscribe);
try {
// 如果有新的订阅选项(即差异化的订阅选项),则进行订阅操作
if (!diffSubscribeOptions.isEmpty()) {
if (DBG) {
Slogf.d(TAG, "subscribeProperty, options: %s", diffSubscribeOptions);
}
// 可能会抛出 ServiceSpecificException
mVehicleHal.subscribeProperty(this, toHalSubscribeOptions(diffSubscribeOptions));
}
// 对于需要取消订阅的属性 ID,调用取消订阅接口
for (int halPropId : propIdsToUnsubscribe) {
if (DBG) {
Slogf.d(TAG, "unsubscribeProperty for property ID: %s",
halPropIdToName(halPropId));
}
// 可能会抛出 ServiceSpecificException
mVehicleHal.unsubscribeProperty(this, halPropId);
}
// 提交订阅更改
mSubManager.commit();
} catch (IllegalArgumentException e) {
// 如果发生参数异常,说明某个属性不支持,应该由调用者确保属性是受支持的
Slogf.e(TAG, "Failed to subscribe/unsubscribe, property is not supported, this should "
+ "not happen, caller must make sure the property is supported", e);
// 丢弃暂存的更改
mSubManager.dropCommit();
return;
} catch (Exception e) {
// 如果发生其他异常,丢弃暂存的更改,并重新抛出异常
mSubManager.dropCommit();
throw e;
}
}
}
updateSubscriptionRateLocked() 方法通过计算当前和暂存状态的差异,向 VHAL 发送订阅或取消订阅请求。
更加迷惑了,刚才
CarPropertyService.applyStagedChangesLocked()里调用过SubscriptionManager.diffBetweenCurrentAndStage()获取当前状态与暂存状态之间的差异了,为什么这里又再次调用? 其实最大的疑问就是为什么在CarPropertyService和PropertyHalService里分别持有SubscriptionManager,在同一个注册流程里,让CarPropertyService持有SubscriptionManager做相应的任务不就行了吗?
调用 VehicleHal.subscribeProperty() 方法将新的订阅请求发送给 VHAL 的这个流程可参考:Android 15 CarService源码04-CarService与Vehicle HAL交互 中的分析。
PropertyHalService.unsubscribeProperty()¶
回到 CarPropertyService.applyStagedChangesLocked() 中,如果 propertyIdsToUnsubscribe 不为空,遍历 propertyIdsToUnsubscribe 列表,逐个取消这些属性的订阅。通过调用 PropertyHalService.unsubscribeProperty() 方法来取消订阅。
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* 取消订阅属性并关闭该属性的更新事件。
*
* @throws ServiceSpecificException 如果VHAL返回错误。
*/
public void unsubscribeProperty(int mgrPropId) throws ServiceSpecificException {
// 如果调试标志DBG为真,则输出日志,打印取消订阅的属性ID
if (DBG) {
Slogf.d(TAG, "unsubscribeProperty mgrPropId=%s",
VehiclePropertyIds.toString(mgrPropId)); // 打印属性ID的字符串表示
}
// 将管理层的属性ID转换为硬件抽象层(VHAL)所需的属性ID
int halPropId = managerToHalPropId(mgrPropId);
// 使用锁确保多线程情况下的线程安全
synchronized (mLock) {
// 即使涉及到binder调用,仍然需要在锁内执行该操作,确保mSubManager的状态与VHAL的状态一致
// mSubManager在此处处理取消订阅的请求
mSubManager.stageUnregister(new ClientType(CAR_PROP_SVC_REQUEST_ID),
new ArraySet<Integer>(Set.of(halPropId))); // 将待取消订阅的属性ID添加到待处理集合中
try {
// 更新订阅率,确保VHAL和mSubManager之间的订阅状态保持一致
updateSubscriptionRateLocked();
} catch (ServiceSpecificException e) {
// 如果更新订阅率时发生异常,输出错误日志并恢复到先前的状态
Slogf.e(TAG, "Failed to update subscription rate for unsubscribe, "
+ "restoring previous state", e);
throw e; // 抛出异常,返回错误状态
}
}
}
}
unsubscribeProperty() 方法的目的是取消订阅一个车载属性,并关闭对该属性的更新事件。mgrPropId 是管理层属性的ID,表示需要取消订阅的车载属性。
先是调用 SubscriptionManager.stageUnregister() 处理取消订阅的请求;最后调用 PropertyHalService.updateSubscriptionRateLocked() 更新订阅率, PropertyHalService.updateSubscriptionRateLocked() 前面分析过了。
SubscriptionManager.stageUnregister()¶
// packages/services/Car/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
public final class SubscriptionManager<ClientType> {
/**
* 准备取消注册属性ID列表。
*
* 该方法将取消注册操作应用于暂存区域,但不会实际提交它们。
*/
public void stageUnregister(ClientType client, ArraySet<Integer> propertyIdsToUnregister) {
// 如果调试标志DBG为真,则输出日志,打印需要取消注册的属性ID列表
if (DBG) {
Slog.d(TAG, "stageUnregister: propertyIdsToUnregister: " + propertyIdsToString(
propertyIdsToUnregister));
}
// 如果暂存区没有被清空,则将当前状态克隆到暂存区
cloneCurrentToStageIfClean();
// 遍历所有需要取消注册的属性ID
for (int i = 0; i < propertyIdsToUnregister.size(); i++) {
int propertyId = propertyIdsToUnregister.valueAt(i);
// 获取指定属性ID的所有区域ID
ArraySet<Integer> areaIds =
mStagedRateInfoByClientByPropIdAreaId.getSecondKeysForFirstKey(propertyId);
// 遍历每个区域ID
for (int j = 0; j < areaIds.size(); j++) {
int areaId = areaIds.valueAt(j);
// 将当前属性ID和区域ID标记为受影响,添加到暂存列表
mStagedAffectedPropIdAreaIds.add(new int[]{propertyId, areaId});
// 获取该属性ID和区域ID对应的客户端订阅信息
RateInfoForClients<ClientType> rateInfoForClients =
mStagedRateInfoByClientByPropIdAreaId.get(propertyId, areaId);
// 如果没有找到该属性和区域的订阅信息,输出错误日志并跳过
if (rateInfoForClients == null) {
Slog.e(TAG, "The property: " + VehiclePropertyIds.toString(propertyId)
+ ", area ID: " + areaId + " was not registered, do nothing");
continue;
}
// 从客户端订阅信息中移除当前客户端的订阅
rateInfoForClients.remove(client);
// 如果该区域的所有客户端订阅都被移除,则删除该区域的订阅信息
if (rateInfoForClients.isEmpty()) {
mStagedRateInfoByClientByPropIdAreaId.remove(propertyId, areaId);
}
}
}
}
}
stageUnregister() 方法用于准备取消对指定属性ID的订阅。它将取消注册操作应用到暂存区,但不会立即提交这些更改。
SubscriptionManager.commit()¶
// packages/services/Car/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
public final class SubscriptionManager<ClientType> {
/**
* 提交暂存的更改。
*
* 这将用暂存区的状态替换当前状态。此方法应在更改成功应用到底层之后调用。
*/
public void commit() {
// 如果暂存区没有被修改(即没有更改需要提交),则直接返回
if (mStagedAffectedPropIdAreaIds.isEmpty()) {
if (DBG) {
// 如果调试标志DBG为真,则输出日志,表示没有更改需要提交
Slog.d(TAG, "No changes has been staged, nothing to commit");
}
return;
}
// 丢弃当前状态,并用暂存区的状态替换当前状态
mCurrentRateInfoByClientByPropIdAreaId = mStagedRateInfoByClientByPropIdAreaId;
// 清空暂存的受影响的属性ID和区域ID列表
mStagedAffectedPropIdAreaIds.clear();
}
}
commit() 方法的作用是提交暂存的更改,将暂存区的状态应用到当前状态,并清空暂存区。
设置属性¶
在前面 Android 15 CarService源码08-CarPropertyService之接口 分析中,我们知道了 CarPropertyManager.setProperty() 最终是会调到 CarPropertyService.setProperty()
CarPropertyService.setProperty()¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
@Override
public void setProperty(CarPropertyValue carPropertyValue,
ICarPropertyEventListener iCarPropertyEventListener)
throws IllegalArgumentException, ServiceSpecificException {
// 确保 iCarPropertyEventListener 不为空
requireNonNull(iCarPropertyEventListener);
// 验证传入的 carPropertyValue 参数是否合法
validateSetParameters(carPropertyValue);
// 获取当前时间,记录操作的开始时间
long currentTimeMs = System.currentTimeMillis();
// 执行同步操作,检查是否超出限制
runSyncOperationCheckLimit(() -> {
// 调用 PropertyHalService 的 setProperty 方法执行实际的属性设置操作
mPropertyHalService.setProperty(carPropertyValue);
return null;
});
// 获取事件监听器的 Binder 对象
IBinder listenerBinder = iCarPropertyEventListener.asBinder();
// 加锁,确保线程安全
synchronized (mLock) {
// 尝试从客户端映射表中获取对应的客户端
CarPropertyServiceClient client = mClientMap.get(listenerBinder);
if (client == null) {
// 如果客户端不存在,创建新的客户端对象
client = new CarPropertyServiceClient(iCarPropertyEventListener,
this::unregisterListenerBinderForProps);
}
// 如果客户端已死,则打印警告信息并返回
if (client.isDead()) {
Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
return;
}
// 这里不调用 addContinuousProperty 或 addOnChangeProperty,因为
// 该客户端不会启用过滤,所以不需要记录这些过滤信息
mClientMap.put(listenerBinder, client);
// 更新设置操作记录器,记录属性 ID 和区域 ID 以及对应的客户端
updateSetOperationRecorderLocked(carPropertyValue.getPropertyId(),
carPropertyValue.getAreaId(), client);
// 如果调试标志 DBG 为真,则输出设置操作的延迟时间
if (DBG) {
Slogf.d(TAG, "Latency of setPropertySync is: %f", (float) (System
.currentTimeMillis() - currentTimeMs));
}
// 记录同步设置操作的延迟时间到延迟直方图中
mSetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
- currentTimeMs));
}
}
}
最重要的就是在同步操作中调用 mPropertyHalService.setProperty(carPropertyValue) 来执行实际的属性设置操作。
但是这里有一个疑问点:调用 mClientMap.put(listenerBinder, client) 将客户端加入到 mClientMap 中。我们在前面分析过监听回调事件就是放到 mClientMap ,所有我当时认为 mClientMap 这个映射表,用于存储和管理监听“全局”的客户端。所以仅仅在一个函数里监听不应该放在这里才对,因为函数执行完了客户端的Listener 就不存在了,那这里又不烧毁。目前想不通,先保留疑问。
PropertyHalService.setProperty()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
private final VehicleHal mVehicleHal;
/**
* 设置车辆属性的值。
*
* @throws IllegalArgumentException 如果参数无效。
* @throws ServiceSpecificException 如果在 HAL 层发生异常。
*/
public void setProperty(CarPropertyValue carPropertyValue)
throws IllegalArgumentException, ServiceSpecificException {
// 定义一个变量,用于存储转换后的 HAL 属性值
HalPropValue valueToSet;
// 使用锁进行同步,保证线程安全
synchronized (mLock) {
// 将 CarPropertyValue 转换为 HalPropValue 对象
valueToSet = carPropertyValueToHalPropValueLocked(carPropertyValue);
}
// 调用 mVehicleHal 的 set 方法来设置属性值
// CarPropertyManager 会捕获并重新抛出异常,所以在此处无需再处理异常。
mVehicleHal.set(valueToSet);
}
}
setProperty() 方法的主要作用是将传入的 CarPropertyValue 设置到车辆硬件抽象层(HAL)中,涉及将车属性值从上层转换为 HAL 层能处理的格式,并通过 mVehicleHal.set() 实现与硬件的交互。
VehicleHal.set()¶
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
public class VehicleHal implements VehicleHalCallback, CarSystemService {
private final VehicleStub mVehicleStub;
/**
* 设置车辆属性值。
*
* @throws IllegalArgumentException 如果传入的参数无效
* @throws ServiceSpecificException 如果 VHAL 层返回错误
*/
public void set(HalPropValue propValue)
throws IllegalArgumentException, ServiceSpecificException {
// 调用带重试机制的 set 方法,尝试设置属性值
setValueWithRetry(propValue);
}
/**
* 带重试机制的设置属性值。
*
* @param value 要设置的属性值
*/
private void setValueWithRetry(HalPropValue value) {
// 调用重试机制来执行设置操作
invokeRetriable((requestValue) -> {
// 启动 trace 跟踪,记录设置操作
Trace.traceBegin(TRACE_TAG, "VehicleStub#set");
// 通过 VehicleStub 设置属性值
mVehicleStub.set(requestValue);
// 结束 trace 跟踪
Trace.traceEnd(TRACE_TAG);
return null;
}, "set", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, /* maxRetries= */ 0);
}
}
VehicleStub 是一个 VehicleStub 类型的成员变量,代表与车辆硬件抽象层(VHAL)的实际通信对象。通过它可以与底层车辆硬件交互,进行属性设置和获取操作。
根据 Android 15 CarService源码02-服务初始化 我们知道这个 mVehicleStub 其实就是 AidlVehicleStub 。
AidlVehicleStub.set()¶
// packages/services/Car/service/src/com/android/car/AidlVehicleStub.java
final class AidlVehicleStub extends VehicleStub {
/**
* 设置一个属性。
*
* @param requestedPropValue 要设置的属性值。
* @throws RemoteException 如果远程操作失败。
* @throws ServiceSpecificException 如果 VHAL 返回特定的服务错误。
*/
@Override
public void set(HalPropValue requestedPropValue) throws RemoteException,
ServiceSpecificException {
// 记录当前时间,便于性能分析
long currentTime = System.currentTimeMillis();
// 获取或设置同步请求,将请求添加到等待池中,并通过异步处理器处理请求
getOrSetSync(requestedPropValue, mPendingSyncSetValueRequestPool,
new AsyncSetRequestsHandler(),
(result) -> {
// 如果请求的状态不为 OK,抛出 ServiceSpecificException 异常
if (result.status != StatusCode.OK) {
throw new ServiceSpecificException(result.status,
"无法设置 " + printPropIdAreaId(requestedPropValue) + " 的值");
}
return null;
});
// 记录设置操作的同步延迟
sVehicleHalSetSyncLatencyHistogram.logSample((float)
(System.currentTimeMillis() - currentTime));
}
}
set() 方法通过同步请求处理机制完成设置操作。这个流程我们在 Android 15 CarService源码04-CarService与Vehicle HAL交互 分析过了。
获取属性¶
在前面 Android 15 CarService源码08-CarPropertyService之接口 分析中,我们知道了 CarPropertyManager.getProperty() 最终是会调到 CarPropertyService.getProperty() ,或者 CarPropertyManager.getPropertyList() 最终是会调到 CarPropertyService.getPropertyList() 。
CarPropertyService.getProperty()¶
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
@Override
public CarPropertyValue getProperty(int propertyId, int areaId)
throws IllegalArgumentException, ServiceSpecificException {
// 验证获取属性的参数是否合法
validateGetParameters(propertyId, areaId);
// 开始性能追踪,用于记录方法的执行时间
Trace.traceBegin(TRACE_TAG, "CarPropertyValue#getProperty");
// 记录当前时间,用于计算方法执行的延迟
long currentTimeMs = System.currentTimeMillis();
try {
// 执行同步操作并检查操作限制,调用属性HAL服务获取指定属性
return runSyncOperationCheckLimit(() -> {
return mPropertyHalService.getProperty(propertyId, areaId);
});
} finally {
// 计算并记录同步操作的延迟时间
if (DBG) {
Slogf.d(TAG, "getPropertySync 的延迟是: %f", (float) (System
.currentTimeMillis() - currentTimeMs));
}
// 记录获取属性的同步延迟
mGetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
- currentTimeMs));
// 结束性能追踪
Trace.traceEnd(TRACE_TAG);
}
}
}
最重要的就是在同步操作中调用 mPropertyHalService.getProperty(propertyId, areaId) 来执行获取属性操作。
PropertyHalService.getProperty()¶
// packages/services/Car/service/src/com/android/car/hal/PropertyHalService.java
public class PropertyHalService extends HalServiceBase {
/**
* 返回属性值。
*
* @param mgrPropId 在 {@link VehiclePropertyIds} 中的属性 ID
* @param areaId 属性的区域 ID
* @throws IllegalArgumentException 如果传入的参数无效
* @throws ServiceSpecificException 如果在 HAL 层发生异常,或者属性状态不可用
*/
public CarPropertyValue getProperty(int mgrPropId, int areaId)
throws IllegalArgumentException, ServiceSpecificException {
// 将管理端的属性 ID 转换为 HAL 层的属性 ID
int halPropId = managerToHalPropId(mgrPropId);
// 定义两个对象来存储从 VHAL 获取的属性值和配置
HalPropValue halPropValue;
HalPropConfig halPropConfig;
// 锁定块,确保线程安全,防止多线程访问时出现问题
synchronized (mLock) {
// 获取与 halPropId 关联的属性配置
halPropConfig = mHalPropIdToPropConfig.get(halPropId);
// 如果是静态和系统属性,且该属性已经被锁定,则尝试从缓存中获取属性值
if (isStaticAndSystemPropertyLocked(mgrPropId)) {
// 从缓存中获取对应的属性值
CarPropertyValue carPropertyValue = mStaticPropertyIdAreaIdCache.get(mgrPropId,
areaId);
// 如果缓存中存在该属性值,则直接返回
if (carPropertyValue != null) {
if (DBG) {
Slogf.d(TAG, "Get Sync Property: %s retrieved from cache",
VehiclePropertyIds.toString(mgrPropId));
}
return carPropertyValue;
}
}
}
// 如果缓存中没有,则从 VHAL 获取该属性的值
halPropValue = mVehicleHal.get(halPropId, areaId);
try {
// 将从 VHAL 获取的 HalPropValue 转换为 CarPropertyValue
CarPropertyValue result = halPropValue.toCarPropertyValue(mgrPropId, halPropConfig);
// 锁定块,确保线程安全,防止多线程访问时出现问题
synchronized (mLock) {
// 如果该属性不是静态和系统属性锁定,则直接返回转换后的结果
if (!isStaticAndSystemPropertyLocked(mgrPropId)) {
return result;
}
// 如果是静态和系统属性锁定,则将结果缓存起来,以便下次使用
mStaticPropertyIdAreaIdCache.put(mgrPropId, areaId, result);
return result;
}
} catch (IllegalStateException e) {
// 如果转换过程失败,则抛出 ServiceSpecificException 异常,并附带详细错误信息
throw new ServiceSpecificException(STATUS_INTERNAL_ERROR,
"无法将 halPropValue 转换为 carPropertyValue,属性:"
+ VehiclePropertyIds.toString(mgrPropId) + " 区域 ID: " + areaId
+ ", 异常:" + e);
}
}
}
- 将
mgrPropId转换为 HAL 层使用的属性 ID,具体转换规则由managerToHalPropId方法实现。 - 获取与
halPropId对应的属性配置。这是从属性配置映射mHalPropIdToPropConfig中获取的,主要用于转换和处理属性值。 - 如果是静态和系统属性且被锁定,则会首先尝试从
mStaticPropertyIdAreaIdCache缓存中获取已存储的属性值。这样可以避免每次都从 VHAL 获取属性,提升性能。 - 通过
mVehicleHal.get方法从 VHAL 层获取该属性的值。
VehicleHal.get()¶
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
public class VehicleHal implements VehicleHalCallback, CarSystemService {
/**
* 返回指定属性 ID 和区域 ID 对应的 {@link HalPropValue}。
*
* @throws IllegalArgumentException 如果传入的参数无效
* @throws ServiceSpecificException 如果 VHAL 返回错误
*/
public HalPropValue get(int propertyId, int areaId)
throws IllegalArgumentException, ServiceSpecificException {
// 如果启用了调试日志,打印获取属性和区域的信息
if (DBG) {
Slogf.d(CarLog.TAG_HAL, "get, " + toPropertyIdString(propertyId)
+ toAreaIdString(propertyId, areaId));
}
// 调用带有重试机制的 getValueWithRetry 方法来获取属性值
return getValueWithRetry(mPropValueBuilder.build(propertyId, areaId));
}
/**
* 使用重试机制来获取属性值。
*
* @param value 要获取的属性值对象
* @return 获取的 HalPropValue 对象
*/
private HalPropValue getValueWithRetry(HalPropValue value) {
// 默认重试次数为 0
return getValueWithRetry(value, /* maxRetries= */ 0);
}
/**
* 使用重试机制来获取属性值,允许设置最大重试次数。
*
* @param value 要获取的属性值对象
* @param maxRetries 最大重试次数
* @return 获取的 HalPropValue 对象
*/
private HalPropValue getValueWithRetry(HalPropValue value, int maxRetries) {
HalPropValue result;
// 记录获取属性值操作的开始时间
Trace.traceBegin(TRACE_TAG, "VehicleStub#getValueWithRetry");
try {
// 调用重试机制进行获取属性值的操作
result = invokeRetriable((requestValue) -> {
// 开始获取属性值的操作
Trace.traceBegin(TRACE_TAG, "VehicleStub#get");
try {
return mVehicleStub.get(requestValue); // 调用实际的 VehicleStub 获取属性值
} finally {
Trace.traceEnd(TRACE_TAG); // 结束属性获取操作的追踪
}
}, "get", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, maxRetries);
} finally {
Trace.traceEnd(TRACE_TAG); // 结束重试操作的追踪
}
// 如果获取的结果为 null,并且 VHAL 返回状态为 OKAY,我们将其视为不可用
if (result == null) {
throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE,
errorMessage("get", value, "VHAL returns null for property value"));
}
// 返回获取到的属性值
return result;
}
}
getValueWithRetry() 实现了带重试机制的属性获取操作。
invokeRetriable() 是一个通用的重试机制,它接受一个 Callable 类型的操作,并执行该操作。如果操作失败,则会根据配置的最大重试次数进行重试,直到成功或超时。这个点在前面的 Android 15 CarService源码04-CarService与Vehicle HAL交互 分析过了。
我们直接看 mVehicleStub.get(requestValue) , 也就是 AidlVehicleStub.get() 。
AidlVehicleStub.get()¶
// packages/services/Car/service/src/com/android/car/AidlVehicleStub.java
final class AidlVehicleStub extends VehicleStub {
/**
* 获取一个属性。
*
* @param requestedPropValue 要获取的属性。
* @return 返回车辆属性值。
* @throws RemoteException 如果远程操作失败。
* @throws ServiceSpecificException 如果 VHAL 返回特定的服务错误。
*/
@Override
@Nullable
public HalPropValue get(HalPropValue requestedPropValue)
throws RemoteException, ServiceSpecificException {
// 记录当前时间,用于计算延迟
long currentTime = System.currentTimeMillis();
// 使用同步操作和重试机制获取属性值
HalPropValue halPropValue = getOrSetSync(requestedPropValue,
mPendingSyncGetValueRequestPool, new AsyncGetRequestsHandler(),
(result) -> {
// 如果获取的状态不是 OK,抛出服务特定的异常
if (result.status != StatusCode.OK) {
throw new ServiceSpecificException(result.status,
"failed to get value for " + printPropIdAreaId(requestedPropValue));
}
// 如果返回的属性值为空,返回 null
if (result.prop == null) {
return null;
}
// 如果属性值存在,构建并返回 HalPropValue 对象
return mPropValueBuilder.build(result.prop);
});
// 记录同步获取属性值的延迟并进行性能分析
sVehicleHalGetSyncLatencyHistogram.logSample((float)
(System.currentTimeMillis() - currentTime));
// 返回获取的 HalPropValue
return halPropValue;
}
}
这里就不再重新分析了,请参考前面的 Android 15 CarService源码04-CarService与Vehicle HAL交互 。
后话¶
像异步设置/获取属性、获取读写权限等接口的流程大同小异。虽然 CarPropertyService 还有很多内容可以讲,但是我在这一服务浪费了太多时间,不想再深入了。