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