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
还有很多内容可以讲,但是我在这一服务浪费了太多时间,不想再深入了。