【Android R】車載 Android 核心服務 - CarPropertyService

語言: CN / TW / HK

theme: simplicity-green

封面座艙配圖 - 上汽飛凡R7

前言

對比開發車載Android和手機Android應用,最大的區別應該就是許多車載應用需要考慮汽車整體的執行狀態,例如,控制車載空調或車速達到一定的閾值時,出於安全的考慮多媒體應用要主動保持靜音;汽車處於行駛狀態下,OTA應用要保持靜默等等。APP如何從Framework層獲取車輛狀態的資料,而Framework層又是從哪裡獲取到資料,它們的執行機制是怎樣的,就是本篇要解釋的問題了。

本文是車載Android核心服務系列文章的第二篇,系列目錄如下:

1.【Android R】車載 Android 核心服務 - CarService 解析

2.【Android R】車載 Android 核心服務 - CarPropertyService 解析

這個系列文章的主要目的在於整理原生車載Android系統中,一些核心Service的執行原理與原始碼實現,所以會有大段的原始碼解讀,內容會比較枯燥。如果閱讀本文時還沒有車載或車載相關的經驗並不豐富,建議先閱讀從應用工程師的角度再談車載 Android 系統,瞭解車載Android系統的基礎結構。

本系列涉及的應用層API以及Framework層的實現方式,基於原生車載Android R系統,由於實際專案中各個主機廠商會對CarService做種種修改,本系列內容僅供車載開發者參考。

CarPropertyService 簡介

通過上一篇的介紹,我們瞭解了狹義上的CarService其實只是一系列Binder物件的容器,本身並沒有多少特殊功能,有過車載經驗的同學可能會有疑問,因為這可能和你正在經歷的專案有出入,這是因為部分量產型的車載專案為了保證關鍵服務的穩定性,CarService中不少功能都被獨立出去了,導致CarService實際上只能提供了查詢、設定車輛屬性的功能,而這部分功能就是本篇的主角 - CarPropertyService實現的。

從實現上來說CarPropertyService的架構如下:

我們從下往上依次來介紹:

  • VehicleHAL

用於接收MCU資料的HAL層程式。VehicleHAL與MCU之間是如何進行通訊的,每個車載專案技術選型不同,實現上也千差萬別,無法詳細介紹。我個人經歷過得的某個車載專案是使用DBUS

  • HalClient

HIDL在Client端的HwBinder物件,實現最基本的HIDL通訊功能。

  • VehicleHal

用於與HAL層的Vehicle HAL程式的通訊介面。它需要對接收到的資料進行基本解析(型別檢查),然後將每個事件傳送到相應的HalServiceBase實現類裡。

由於Framework層的VehicleHal與HAL層的VehicleHAL存在重名,後面為了區分會用VehicleHal(FWK) 表示Framework層的VehicleHal,用VehicleHAL(HAL) 表示HAL層的VehicleHAL。

  • PropertyHalService

負責進一步處理來自VehicleHal(FWK)資料的介面。是HalServiceBase的實現類。

  • CarPropertyService

ICarProperty.aidl的實現類。是應用層與HAL層的通訊中繼。

  • CarPropertyManager

CarPropertyService在Client端的代理。車載系統中的應用需要通過CarPropertyManager來獲取或設定車輛的屬性。

先來看 CarPropertyManager 提供的API。

車輛屬性 API

在Android R中CarInfoManagerCarCabinManagerCarHvacManagerCarSensorManagerCarVendorExtensionManager均已經過時,在我個人實際經歷的車載專案中,負責開發CarService的同事,也會選擇將以上Manager移除使用CarPropertyManager替代。

雖然將汽車的Property屬性分散到獨立的Manager中可以讓Car API的易用性、可讀性更強,但是隨著汽車屬性的不斷增加,API的維護也會變得愈加複雜,而CarPropertyManager從實現上就讓維護工作變得簡單,Google可能也是基於以上的考慮選擇不再維護獨立的Manager。

所以本文不再介紹CarInfoManagerCarCabinManagerCarHvacManagerCarSensorManagerCarVendorExtensionManager的實現,有需要的同學請參考原始碼中是如何實現的。

CarPropertyManager API 介紹

CarPropertyManager 中定義的常量

| 型別 | 常量名 | | ----- | --------------------------------------------------------------------- | | int | CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED 表示設定操作失敗的狀態,汽車拒絕訪問。 | | int | CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG 表示設定操作失敗的狀態,引數無效。 | | int | CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE 表示設定操作失敗的狀態,屬性不可用。 | | int | CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN 表示設定操作失敗的狀態,重新嘗試。 | | int | CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN 表示設定操作失敗的狀態,未知錯誤。 | | float | SENSOR_RATE_FAST 以10Hz的速率讀取感測器。 | | float | SENSOR_RATE_FASTEST 以100Hz的速率讀取感測器。 | | float | SENSOR_RATE_NORMAL 以1Hz的速率讀取感測器。 | | float | SENSOR_RATE_ONCHANGE 讀取ON_CHANGE感測器 | | float | SENSOR_RATE_UI 以5Hz的速率讀取感測器。 |

CarPropertyManager 中定義的方法。

| 返回值型別 | 方法名 | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | int | getAreaId(int propId, int area) 返回包含車輛屬性選定區域的areaId。 | | boolean | getBooleanProperty(int prop, int area) 返回bool型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | CarPropertyConfig<?> | getCarPropertyConfig(int propId) 按屬性Id獲取CarPropertyConfig。 | | float | getFloatProperty(int prop, int area) 返回float型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | int[] | getIntArrayProperty(int prop, int area) 返回int陣列型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | int | getIntProperty(int prop, int area) 返回int型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | CarPropertyValue | getProperty(Class clazz, int propId, int areaId) 返回CarPropertyValue型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | CarPropertyValue | getProperty(int propId, int areaId) | | List | getPropertyList(ArraySet propertyIds) | | List | getPropertyList() | | boolean | isPropertyAvailable(int propId, int area) 根據汽車的當前狀態,檢查給定屬性是否可用或禁用。 | | boolean | registerCallback(CarPropertyManager.CarPropertyEventCallback callback, int propertyId, float rate) 註冊CarPropertyEventCallback以獲取車輛屬性更新。 | | void | setBooleanProperty(int prop, int areaId, boolean val) 修改屬性。 | | void | setFloatProperty(int prop, int areaId, float val) 設定float型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | void | setIntProperty(int prop, int areaId, int val) 設定int型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 | | void | setProperty(Class clazz, int propId, int areaId, E val) 按areaId設定車輛屬性的值。 | | void | unregisterCallback(CarPropertyManager.CarPropertyEventCallback callback) 停止監聽車輛屬性的更新回撥 | | void | unregisterCallback(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 中定義的常量。 | 型別 | 常量名 | | --- | -------------------------------------------------------- | | int | VEHICLE_PROPERTY_ACCESS_NONE 屬性訪問許可權未知 | | int | VEHICLE_PROPERTY_ACCESS_READ 該屬性是可讀的 | | int | VEHICLE_PROPERTY_ACCESS_READ_WRITE 該屬性是可讀、可寫的 | | int | VEHICLE_PROPERTY_ACCESS_WRITE 該屬性是可寫的 | | int | VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS 這種屬性值會以一定的頻率不斷上報 | | int | VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE 該屬性的值會在發生變化時上報 | | int | VEHICLE_PROPERTY_CHANGE_MODE_STATIC 該屬性的值始終不會改變 |

CarPropertyConfig 中定義的方法 | 返回值型別 | 方法名 | | ------------- | -------------------------------------------- | | int | getAccess() 返回汽車屬性的訪問型別。具體型別就是上面定義的前4個常量 | | int[] | getAreaIds() 返回汽車的區域id陣列 | | int | getAreaType() 返回汽車屬性的區域型別。 | | int | getChangeMode() 返回汽車屬性的更改模式。具體模式就是上面定義的後3個常量 | | List | getConfigArray() 返回額外的配置屬性 | | float | getMaxSampleRate() 返回最大頻率。僅支援持續上報的屬性 | | float | getMinSampleRate() 返回最小頻率。僅支援持續上報的屬性 | | T | getMaxValue(int areaId) | | T | getMaxValue() | | T | getMinValue() | | T | getMinValue(int areaId) | | int | getPropertyId() 返回屬性ID | | Class | getPropertyType() 返回車輛屬性的型別 | | boolean | isGlobalProperty() 返回 是否是全域性屬性 |

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 mHvacPropertyIds = new ArraySet<>(Arrays.asList(new Integer [] { ... }));

CarPropertyManager propertyMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE); List 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 getBooleanProperty(int propertyId, int area) { return propertyMgr.getProperty(Boolean.class, propertyId, area); }

CarPropertyValue 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()方法為例,看一下是如何限制無許可權應用的呼叫的。

``` @Override public CarPropertyValue getProperty(int prop, int zone) { ... ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop)); return mHal.getProperty(prop, zone); }

@Nullable public String getReadPermission(int mgrPropId) { int halPropId = managerToHalPropId(mgrPropId); return mPropIds.getReadPermission(halPropId); }

@Nullable public String getReadPermission(int propId) { Pair p = mProps.get(propId); if (p != null) { // 屬性ID存在。返回 許可權。 if (p.first == null) { Log.e(TAG, "propId is not available for reading : 0x" + toHexString(propId)); } return p.first; } else if (isVendorProperty(propId)) { // 如果屬性是供應商屬性,並且沒有特定許可權。 return Car.PERMISSION_VENDOR_EXTENSION; } else { return null; } } ```

getReadPermission的呼叫鏈很好懂,主要就是將傳入的id與PropertyHalServiceIds中關聯好的許可權比對,並取出對應的許可權字串。

assertPermission方法會判斷呼叫方是否擁有相應的許可權,或本次呼叫是否是自身發起的,如果不是,則會丟擲SecurityException。

```

###ICarImpl.java

public static void assertPermission(Context context, String permission) { if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("requires " + permission); } } ```

setProperty()方法的許可權檢查與getProperty()方法類似,不過getProperty()方法是檢查ReadPermission,setProperty()方法是檢查WritePermission。

車輛屬性的讀取以及操作需要慎重授權給應用,所以許可權控制在車載Android系統中就顯得尤為重要。

VehicleHAL

VehicleHAL 是由Android Automotive OS定義的硬體抽象層(hardware abstract layer)。它定義了 OEM 可以實現的屬性以及與Framework Service互動的介面。使用者對車輛APP產生的一系列操作最終都會來到VehicleHAL,並由於VehicleHAL轉發出Android系統。

原始碼地址:/hardware/interfaces/automotive/vehicle/2.0/

原始碼結構

VehicleHAL主要由IPC介面互動邏輯兩部分組成,如下所示

  • IVehicle.hal

定義VehicleHAL對外暴露的方法。

  • IVehicleCallback.hal

定義屬性變化時的回撥介面

  • types.hal

定義在IVehicle.halIVehicleCallback.hal中使用的資料結構和屬性值。

上述三個hal檔案的結構與AIDL的通訊結構非常相似,不過這種通訊方式叫做HIDL(讀作:嗨豆)。

有關HIDL的進一步內容,請參考官方文件:https://source.android.google.cn/docs/core/architecture/hidl

  • default/ utils/

是Android Automotive OS對於VechicleHAL的參考實現。但是其中並沒有實現與車輛匯流排進行資料互動這樣的業務邏輯,這塊的內容需要主機制造商自行實現。

VehicleHAL 介面

VHAL 支援以下介面:

  • getAllPropConfigs() generates (vec propConfigs);

列出 VehicleHAL 所支援的所有屬性的配置。CarService 僅使用支援的屬性。

  • getPropConfigs(vec props) generates (StatusCode status, vec propConfigs);

返回所選屬性的配置。

  • get(VehiclePropValue requestedPropValue) generates (StatusCode status, VehiclePropValue propValue);

獲取車輛屬性值。

  • set(VehiclePropValue propValue) generates (StatusCode status);

向屬性寫入一個值。寫入的結果是按屬性進行定義的。

  • subscribe(IVehicleCallback callback, vec options) generates (StatusCode status);

開始監視屬性值的變化。

  • unsubscribe(IVehicleCallback callback, int32_t propId) generates (StatusCode status);

取消訂閱屬性事件。

VHAL 支援以下回調介面:

  • oneway onPropertyEvent(vecpropValues);

通知車輛屬性值的變化。應只針對已訂閱屬性執行。

  • oneway onPropertySet(VehiclePropValue propValue);

如果客戶端使用SubscribeFlags.EVENTS_FROM_ANDROID標誌訂閱了屬性,並且呼叫了IVehicle.set()方法,則會呼叫此方法。

  • oneway onPropertySetError(StatusCode errorCode,int32_t propId,int32_tareaId);

返回全域性 VHAL 級錯誤或每個屬性的錯誤。全域性錯誤會導致 HAL 重新啟動,這可能會導致包括應用在內的其他元件重新啟動。

編譯VehicleHAL

HIDL介面定義好之後,與AIDL介面一樣需要編譯更jar提供給Framework的開發,以下步驟是編譯原生的VehicleHAL,實際專案中的VehicleHAL一般會放置vendor裡面,但是編譯方式一樣。

cd hardware/interfaces/automotive/vehicle/2.0 mma

編譯好的jar包位於

/out/soong/.intermediates/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle-V2.0-java/android_common/javac

如下圖所示:

由於博主並沒有實際從事過HAL層的開發,有關VehicleHAL就只介紹到這裡,實際工作中一般會有單獨負責HAL層的同事編寫這裡的程式碼。

更多內容請參考官方的文件:https://source.android.google.cn/docs/devices/automotive/vhal

總結

本篇介紹了CarPropertyService的實現原理,但是僅通過閱讀這篇文章其實並不能完全掌握整個CarPropertyService,這是任何技術性文章都做不到的通病,實際專案依然需要我們仔細閱讀原始碼,分析方法的含義,本篇文章的實際目的是讓你弄清楚關鍵節點的實現方式和執行原理。

我個人也負責過CarPropertyService的開發工作,當時由於對CarPropertyService的執行機制並不瞭解,選擇整個重寫CarPropertyService,現在想想著實走了不少彎路。

好了,感謝你的閱讀,希望能幫助到你。