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

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

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

类型常量名
intCAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED 表示设置操作失败的状态,汽车拒绝访问。
intCAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG 表示设置操作失败的状态,参数无效。
intCAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE 表示设置操作失败的状态,属性不可用。
intCAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN 表示设置操作失败的状态,重新尝试。
intCAR_SET_PROPERTY_ERROR_CODE_UNKNOWN 表示设置操作失败的状态,未知错误。
floatSENSOR_RATE_FAST 以10Hz的速率读取传感器。
floatSENSOR_RATE_FASTEST 以100Hz的速率读取传感器。
floatSENSOR_RATE_NORMAL 以1Hz的速率读取传感器。
floatSENSOR_RATE_ONCHANGE 读取ON_CHANGE传感器
floatSENSOR_RATE_UI 以5Hz的速率读取传感器。

CarPropertyManager 中定义的方法。

返回值类型方法名
intgetAreaId(int propId, int area) 返回包含车辆属性选定区域的areaId。
booleangetBooleanProperty(int prop, int area) 返回bool类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
CarPropertyConfig<?>getCarPropertyConfig(int propId) 按属性Id获取CarPropertyConfig。
floatgetFloatProperty(int prop, int area) 返回float类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
int[]getIntArrayProperty(int prop, int area) 返回int数组类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
intgetIntProperty(int prop, int area) 返回int类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
CarPropertyValuegetProperty(Class clazz, int propId, int areaId) 返回CarPropertyValue类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
CarPropertyValuegetProperty(int propId, int areaId)
ListgetPropertyList(ArraySet propertyIds)
ListgetPropertyList()
booleanisPropertyAvailable(int propId, int area) 根据汽车的当前状态,检查给定属性是否可用或禁用。
booleanregisterCallback(CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float rate) 注册CarPropertyEventCallback以获取车辆属性更新。
voidsetBooleanProperty(int prop, int areaId, boolean val) 修改属性。
voidsetFloatProperty(int prop, int areaId, float val) 设置float类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
voidsetIntProperty(int prop, int areaId, int val) 设置int类型的车辆属性,此方法可能需要几秒钟才能完成,因此需要从非主线程调用它。
voidsetProperty(Class clazz, int propId, int areaId, E val) 按areaId设置车辆属性的值。
voidunregisterCallback(CarPropertyManager.CarPropertyEventCallback callback) 停止监听车辆属性的更新回调
voidunregisterCallback(CarPropertyManager.CarPropertyEventCallback callback, int propertyId) 停止监听车辆属性的更新回调

setXXXProperty/getXXXProperty默认只实现了对float、int、boolean、intArray类型的拓展,如需要使用更多的类型,可以使用setProperty/getProperty(Class class)传入需要拓展的类型即可。

CarPropertyManager 的实现原理并不复杂,可以直接参考源码:/packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java

CarPropertyConfig API 介绍

CarPropertyConfig表示有关汽车属性的一般信息,例如汽车区域的数据类型和最小/最大范围(如果适用)。也是实际开发中非常常用的类。

CarPropertyConfig 中定义的常量。

类型常量名
intVEHICLE_PROPERTY_ACCESS_NONE 属性访问权限未知
intVEHICLE_PROPERTY_ACCESS_READ 该属性是可读的
intVEHICLE_PROPERTY_ACCESS_READ_WRITE 该属性是可读、可写的
intVEHICLE_PROPERTY_ACCESS_WRITE 该属性是可写的
intVEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS 这种属性值会以一定的频率不断上报
intVEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE 该属性的值会在发生变化时上报
intVEHICLE_PROPERTY_CHANGE_MODE_STATIC 该属性的值始终不会改变

CarPropertyConfig 中定义的方法

返回值类型方法名
intgetAccess() 返回汽车属性的访问类型。具体类型就是上面定义的前4个常量
int[]getAreaIds() 返回汽车的区域id数组
intgetAreaType() 返回汽车属性的区域类型。
intgetChangeMode() 返回汽车属性的更改模式。具体模式就是上面定义的后3个常量
ListgetConfigArray() 返回额外的配置属性
floatgetMaxSampleRate() 返回最大频率。仅支持持续上报的属性
floatgetMinSampleRate() 返回最小频率。仅支持持续上报的属性
TgetMaxValue(int areaId)
TgetMaxValue()
TgetMinValue()
TgetMinValue(int areaId)
intgetPropertyId() 返回属性ID
ClassgetPropertyType() 返回车辆属性的类型
booleanisGlobalProperty() 返回 是否是全局属性
CarPropertyManager 使用示例

使用CarPropertyManager可以分为以下几个步骤:

1)使用Car连接到 CarService ,并获取到 CarPropertyManager

Car car = Car.createCar(this, workThreadHandler, 2000, new Car.CarServiceLifecycleListener() {
    @Override
    public void onLifecycleChanged(@NonNull Car car, boolean ready) {
        // ready 在Service断开连接时会变为false
if (ready) {
            CarPropertyManager propertyMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
           
        } else {
            // CarService 发生异常或连接被断开了,需要client端处理。
}
    }
});


2)给所有的property属性注册监听事件

 // 空调的property id list,需要看hal层是如何定义的
 private final ArraySet<Integer> mHvacPropertyIds = new ArraySet<>(Arrays.asList(new Integer [] {
            ...
    }));

CarPropertyManager propertyMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
List<CarPropertyConfig> propertyList = propertyMgr.getPropertyList(mHvacPropertyIds);
for (CarPropertyConfig config : propertyList) {
    // 给每个单独的propertyId注册监听回调。
propertyMgr.registerCallback(callback,config.getPropertyId(), SENSOR_RATE_ONCHANGE);
}


3)获取单个Property的值

public boolean getBooleanProperty(@PropertyId int propertyId, int area) {
    return propertyMgr.getBooleanProperty(propertyId, area);
}


虽然使用getBooleanProperty、getIntProperty、getFloatProperty、getIntArrayProperty代码上更简洁一些,但是更建议使用getProperty()getProperty()的返回值是CarPropertyValue,这其中包含了Property的状态信息,可以让使用方覆盖更多的异常场景。

当属性不可用时,getXXXProperty()会返回默认的值,造成使用方读取数据不准确。

public CarPropertyValue<Boolean> getBooleanProperty(int propertyId, int area) {
    return propertyMgr.getProperty(Boolean.class, propertyId, area);
}

CarPropertyValue<Boolean> value = getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, 0);
if (value == null && value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
    // ac 不可用
} else if (value.getValue()) {
    // ac 开
} else {
    // ac 关
}


4)设定单个Property的值

public void setBooleanProperty(@PropertyId int propertyId, int area, boolean val) {
    if (mHvacPropertyIds.contains(propertyId)) {
        propertyMgr.setBooleanProperty(propertyId, area, val);
    }
}


设定的值最终会通过aidl接口,将数据传输到CarPropertyService中,接下来我们继续看数据在CarPropertyService中是如何传递的。

CarPropertyService 实现原理

CarPropertyService 初始化流程

CarPropertyService是在CarService中完成创建的,CarService的初始化流程在之前的文章【Android R】车载 Android 核心服务 - CarService 解析中已经有过介绍,不再赘述。CarPropertyService的初始流程分为以下4步:

1)首先,在ICarImpl中创建VehicleHal(FWK);

@VisibleForTesting
ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
         CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
         @Nullable CarUserService carUserService,
         @Nullable CarWatchdogService carWatchdogService) {
    ...
    mHal = new VehicleHal(serviceContext, vehicle);
    // 在任何其他服务组件之前执行此操作,以允许进行功能检查。即使没有初始化,它也应该工作。
 // 为此,vhal-get会被重试,因为它可能太早了。
VehiclePropValue disabledOptionalFeatureValue = mHal.getIfAvailableOrFailForEarlyStage(
            VehicleProperty.DISABLED_OPTIONAL_FEATURES, INITIAL_VHAL_GET_RETRY);
    String[] disabledFeaturesFromVhal = null;
    if (disabledOptionalFeatureValue != null) {
        String disabledFeatures = disabledOptionalFeatureValue.value.stringValue;
        if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
            disabledFeaturesFromVhal = disabledFeatures.split(",");
        }
    }
    if (disabledFeaturesFromVhal == null) {
        disabledFeaturesFromVhal = new String[0];
    }
    ...
}


2)在 VehicleHal(FWK) 创建过程中,同时创建出 PropertyHalService 和 HalClient;

public VehicleHal(Context context, IVehicle vehicle) {
    ...
    mPropertyHal = new PropertyHalService(this);
    ...
mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/ );
}


3)然后,在ICarImpl中创建 CarPropertyService;

ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
         CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
         @Nullable CarUserService carUserService,
         @Nullable CarWatchdogService carWatchdogService) {
    ...
    mCarPropertyService = new CarPropertyService(serviceContext, mHal.getPropertyHal());
    ...
}


4)最后,在 ICarImpl 中调用 VehicleHal.init() CarPropertyService.init() 完成初始化。

@MainThread
void init() {
    mHal.init();
    for (CarServiceBase service : mAllServices) {
        service.init();
    }
}


接下来,我们依次把这些模块是如何实现数据上报的流程梳理一下,先来看处于Framework最底层的 HalClient。


HalClient

车辆HAL客户端。直接与车辆HAL的HIDL接口IVehicle交互。包含一些可检索属性的逻辑,将车辆通知重定向到给定的looper线程中。

HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback,
          int waitCapMs, int sleepMs) {
    mVehicle = vehicle;
    Handler handler = new CallbackHandler(looper, callback);
    mInternalCallback = new VehicleCallback(handler);
    mWaitCapMs = waitCapMs;
    mSleepMs = sleepMs;
}


VehicleCallback 是HIDL接口IVehicleCallback.Stub的实现类,负责监听HAL层上报的数据,然后将其发送到CallbackHandler中进行处理。

    private static final class VehicleCallback extends IVehicleCallback.Stub {
        private final Handler mHandler;

        VehicleCallback(Handler handler) {
            mHandler = handler;
        }

        @Override
        public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
            mHandler.sendMessage(Message.obtain(
                    mHandler, CallbackHandler.MSG_ON_PROPERTY_EVENT, propValues));
        }

        @Override
        public void onPropertySet(VehiclePropValue propValue) {
            mHandler.sendMessage(Message.obtain(
                    mHandler, CallbackHandler.MSG_ON_PROPERTY_SET, propValue));
        }

        @Override
        public void onPropertySetError(int errorCode, int propId, int areaId) {
            mHandler.sendMessage(Message.obtain(
                    mHandler, CallbackHandler.MSG_ON_SET_ERROR,
                    new PropertySetError(errorCode, propId, areaId)));
        }
    }


CallbackHandler是一个自定义的Handler,会将VehicleHal(HAL)上报的数据分类通过callback回调给VehicleHal(FWK)。

private static final class CallbackHandler extends Handler {
    private static final int MSG_ON_PROPERTY_SET = 1;
    private static final int MSG_ON_PROPERTY_EVENT = 2;
    private static final int MSG_ON_SET_ERROR = 3;
    ...
    @Override
    public void handleMessage(Message msg) {
        IVehicleCallback callback = mCallback.get();
        ...
        try {
            switch (msg.what) {
                case MSG_ON_PROPERTY_EVENT:
                    callback.onPropertyEvent((ArrayList<VehiclePropValue>) msg.obj);
                    break;
                case MSG_ON_PROPERTY_SET:
                    callback.onPropertySet((VehiclePropValue) msg.obj);
                    break;
                case MSG_ON_SET_ERROR:
                    PropertySetError obj = (PropertySetError) msg.obj;
                    callback.onPropertySetError(obj.errorCode, obj.propId, obj.areaId);
                    break;
                default:
                    Log.e(TAG, "Unexpected message: " + msg.what);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Message failed: " + msg.what);
        }
    }
}


思考一个问题,为什么HAL上报的数据信息要先经过Handler再处理呢?

这既有线程切换的考虑,还有就是VehicleHAL(HAL)上报的数据有时会非常频繁,将数据放到Looper的MessageQueue中可以便于我们按照上报的顺序,有序地处理数据。

VehicleHal

用于与HAL层的Vehicle HAL程序的通信接口。将HalClient回调过来的数据,进行初步的处理。我们以onPropertyEvent为例,看一下VehicleHAl(FWK)是怎么处理HalClient回调过来的数据的。

VehicleHAl(FWK)的处理方式分为两步

1)第一步,根据上报数据找到对应的HalServiceBase(HalServiceBase是PropertyHalService的父类),将VehiclePropValue添加到PropertyHalService的list中。

@Override
public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
    synchronized (mLock) {
        for (VehiclePropValue v : propValues) {
            HalServiceBase service = mPropertyHandlers.get(v.prop);
            if(service == null) {
                Log.e(CarLog.TAG_HAL, "HalService not found for prop: 0x"
                        + toHexString(v.prop));
                continue;
            }
            service.getDispatchList().add(v);
            mServicesToDispatch.add(service);
            ...
        }
    }
    ...
}


2)第二步,主动触发PropertyHalService.onHalEvents()将VehiclePropValue发送到PropertyHalService中,紧接着清理掉缓存数据。

@Override
public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
    ...
    for (HalServiceBase s : mServicesToDispatch) {
        s.onHalEvents(s.getDispatchList());
        s.getDispatchList().clear();
    }
    mServicesToDispatch.clear();
}


PropertyHalService

PropertyHalService.onHalEvents中处理接收到的value list。将数据转换为CarPropertyValue后,通过PropertyHalListener将处理好的数据回调给CarPropertyService,而最终会由CarPropertyService将数据回调会应用层的接口。

@Override
public void onHalEvents(List<VehiclePropValue> values) {
    PropertyHalListener listener;
    ...
    if (listener != null) {
        for (VehiclePropValue v : values) {
            if (v == null) {
                continue;
            }
            ...
            int mgrPropId = halToManagerPropId(v.prop);
            CarPropertyValue<?> propVal;
            if (isMixedTypeProperty(v.prop)) {
                // parse mixed type property value.
VehiclePropConfig propConfig;
                synchronized (mLock) {
                    propConfig = mHalPropIdToVehiclePropConfig.get(v.prop);
                }
                boolean containBooleanType = propConfig.configArray.get(1) == 1;
                propVal = toMixedCarPropertyValue(v, mgrPropId, containBooleanType);
            } else {
                propVal = toCarPropertyValue(v, mgrPropId);
            }
            // 封装到 CarPropertyEvent
CarPropertyEvent event = new CarPropertyEvent(
                    CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
            mEventsToDispatch.add(event);
        }
        listener.onPropertyChange(mEventsToDispatch);
        mEventsToDispatch.clear();
    }
}


注意两个方法,toMixedCarPropertyValue()toCarPropertyValue() 如果数据类型是Integer、Float、Long、Float[]、Long[]、Integer[]、byte[]、String则由 toCarPropertyValue()负责数据转换。除此以外的类型由toMixedCarPropertyValue()负责数据转换。它们都是将VehiclePropValue转换为CarPropertyValue


设定/获取 Property

设定和与获取Property的流程并不复杂,这里就不再粘贴源码了逐个讲解了,贴上一份设定的时序图。

权限控制

上面在分析CarPropertyService监听属性变化的具体实现时,我们提到了使用Car API的接口需要注册对应的权限,那么这些权限是如何管理的呢?

在PropertyHalService的构造方法中,创建了一个PropertyHalServiceIds的对象,而这个对象就是用来存储每个属性所需要的权限的。

PropertyHalServiceIds源码位置:/packages/services/Car/service/src/com/android/car/hal/PropertyHalServiceIds.java

public PropertyHalService(VehicleHal vehicleHal) {
    mPropIds = new PropertyHalServiceIds();
    mSubscribedHalPropIds = new HashSet<Integer>();
    mVehicleHal = vehicleHal;
}


在PropertyHalServiceIds的构造方法中,将每个属性对应需要的权限进行了一一关联,保存在一个SparseArray中。

那么接下来我们以getProperty()方法为例,看一下是如何限制无权限应用的调用的。

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

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

mg-blog.csdnimg.cn/img_convert/de328ce1b8d776a2ce697685d8f4fff3.png)

那么接下来我们以getProperty()方法为例,看一下是如何限制无权限应用的调用的。

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


[外链图片转存中…(img-noa2QKva-1715907498707)]
[外链图片转存中…(img-iCqr8b7c-1715907498708)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值