【Android R】車載 Android 核心服務 - CarPropertyService
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中CarInfoManager
、CarCabinManager
、CarHvacManager
、CarSensorManager
、CarVendorExtensionManager
均已經過時,在我個人實際經歷的車載專案中,負責開發CarService
的同事,也會選擇將以上Manager移除使用CarPropertyManager
替代。
雖然將汽車的Property屬性分散到獨立的Manager中可以讓Car API的易用性、可讀性更強,但是隨著汽車屬性的不斷增加,API的維護也會變得愈加複雜,而CarPropertyManager
從實現上就讓維護工作變得簡單,Google可能也是基於以上的考慮選擇不再維護獨立的Manager。
所以本文不再介紹CarInfoManager
、CarCabinManager
、CarHvacManager
、CarSensorManager
、CarVendorExtensionManager
的實現,有需要的同學請參考原始碼中是如何實現的。
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型別的車輛屬性,此方法可能需要幾秒鐘才能完成,因此需要從非主執行緒呼叫它。 |
|
setXXXProperty/getXXXProperty預設只實現了對float、int、boolean、intArray型別的拓展,如需要使用更多的型別,可以使用setProperty/getProperty(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
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
CarPropertyManager propertyMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
List
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
CarPropertyValue
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
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系統。
原始碼結構
VehicleHAL主要由IPC介面和互動邏輯兩部分組成,如下所示
- IVehicle.hal
定義VehicleHAL對外暴露的方法。
- IVehicleCallback.hal
定義屬性變化時的回撥介面
- types.hal
定義在IVehicle.hal
與IVehicleCallback.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(vec
propValues);
通知車輛屬性值的變化。應只針對已訂閱屬性執行。
- 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
,現在想想著實走了不少彎路。
好了,感謝你的閱讀,希望能幫助到你。
- Android車載應用開發與分析(1) - Android Automotive概述與編譯
- 【Android R】車載 Android 核心服務 - CarService 解析
- 【Android R】車載 Android 核心服務 - CarPropertyService
- 車載Android程式設計師的2022年終總結與轉行建議
- 從應用工程師的角度再談車載 Android 系統
- Android 車載應用開發與分析(12) - SystemUI (一)
- RE: 從零開始的車載Android HMI(三) - SurfaceView
- RE: 從零開始的車載Android HMI(二) - Widget
- RE: 從零開始的車載Android HMI(一) - Lottie
- Android 車載應用開發與分析 (3)- 構建 MVVM 架構(Java版)
- Android 車載應用開發與分析 (4)- 編寫基於AIDL 的 SDK