Android Car CarProperty 车辆信号链路

一篇讲透 Android Car CarProperty 车辆信号链路(保姆级 / 零基础)

适用对象:刚接触车机 Android(AAOS)开发的同学。 目标:把"一个 App 怎么读到车速、怎么控制车窗"这件事,从最上面的应用一路追到最底层的 VHAL,每一层是哪个类、调了哪个方法 ,全部讲清楚。 本文所有类名、方法名、行号均来自 AOSP packages/services/Car 实际源码,可对照阅读。


0. 先用大白话理解:CarProperty 到底是什么?

车上有几千个"数据点":车速、油量、空调温度、车窗开度、档位、车门状态...... 在 Android 车机里,这些数据点被统一抽象成一个东西,叫 Property(属性)

你可以把它想象成一张巨大的 Excel 表:

列名 含义 举例
propertyId 这是"哪一个"信号 PERF_VEHICLE_SPEED(车速)
areaId 这是"哪个位置"的信号 左前门 / 右后门
value 当前的值 60.0(km/h)
status 这个值能不能信 AVAILABLE / UNAVAILABLE
timestamp 这个值是什么时候的 开机以来的纳秒数

App 想干的事其实就三件:

  1. 读一次(getProperty)------"现在车速多少?"
  2. 写一次(setProperty)------"把主驾车窗降下来"
  3. 订阅(registerCallback)------"车速一变就通知我"

整篇文章,就是讲这三件事在系统里是怎么一层层往下传、又怎么一层层传回来的。


1. 全景图:一张图看懂 5 层架构

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│  ① 应用层 App                                                 │
│     你的 Activity / Service                                   │
│     拿到 CarPropertyManager,调 get/set/registerCallback      │
└───────────────────────────┬─────────────────────────────────┘
                            │  普通 Java 方法调用
┌───────────────────────────▼─────────────────────────────────┐
│  ② Car SDK 层 (car-lib,跑在你 App 进程里)                     │
│     CarPropertyManager                                       │
└───────────────────────────┬─────────────────────────────────┘
                            │  Binder 跨进程 (ICarProperty.aidl)
┌───────────────────────────▼─────────────────────────────────┐
│  ③ CarService 层 (com.android.car 进程 / system_server 旁)    │
│     CarPropertyService  ←权限校验、客户端管理                  │
│            │                                                 │
│     PropertyHalService  ←ID 转换、数据格式转换                │
└───────────────────────────┬─────────────────────────────────┘
                            │  Java 方法 → VehicleStub
┌───────────────────────────▼─────────────────────────────────┐
│  ④ VHAL 客户端胶水层                                          │
│     VehicleHal → VehicleStub → AidlVehicleStub               │
└───────────────────────────┬─────────────────────────────────┘
                            │  Binder 跨进程 (IVehicle.aidl, HIDL/AIDL HAL)
┌───────────────────────────▼─────────────────────────────────┐
│  ⑤ VHAL 服务端 + 车身网络 (厂商实现,C++)                      │
│     IVehicle 实现 → CAN/LIN/以太网 → MCU → 真正的车           │
└─────────────────────────────────────────────────────────────┘

记住一句话: 上面两层(①②)和你的 App 在同一个进程;从第 ③ 层开始就跨进程了,靠 Binder 通信;第 ⑤ 层是厂商的 C++ 代码,已经贴着硬件了。


2. 准备工作:App 怎么拿到 CarPropertyManager?

App 不能直接 new 一个 Manager,必须先连上 Car 服务,再"要"一个 Manager 出来。

java 复制代码
// 1. 创建 Car 对象并连接 CarService
Car car = Car.createCar(context);

// 2. 向 Car 要 PROPERTY_SERVICE 对应的管理器
CarPropertyManager mgr =
        (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);

类比:Car 就像安卓里的 ContextgetCarManager() 就像 getSystemService()。 你要 WiFi 功能就 getSystemService(WIFI_SERVICE),要车辆属性就 getCarManager(PROPERTY_SERVICE)

拿到的 CarPropertyManager 内部持有一个 Binder 代理 mService(类型 ICarProperty),这是它能跟 CarService 通信的关键:

  • car-lib/src/android/car/hardware/property/CarPropertyManager.java:91

    java 复制代码
    private final ICarProperty mService;

3. 写方向:setProperty ------ "把车窗降下来"

我们以"设置一个属性"为例,一层层往下追。

第 ② 层 · CarPropertyManager.setProperty()

文件:car-lib/src/android/car/hardware/property/CarPropertyManager.java:1963

java 复制代码
public <E> void setProperty(Class<E> clazz, int propertyId, int areaId, E val) {
    assertPropertyIdIsSupported(propertyId);      // ① 先检查这个属性支不支持
    runSyncOperation(() -> {
        mService.setProperty(                     // ② 通过 Binder 调到 CarService
            new CarPropertyValue<>(propertyId, areaId, val),
            mCarPropertyEventToService);
        return null;
    });
}

这一层做了什么:

  1. 先确认这个 propertyId 在车上真的存在(assertPropertyIdIsSupported)。
  2. 把你的"属性号 + 区域 + 值"打包成一个 CarPropertyValue 对象。
  3. 通过 mService.setProperty(...) 这行------注意这里就跨进程了------把请求丢给 CarService。

小白疑问:为什么要 runSyncOperation? 因为 set 操作要等车端返回结果,可能要几百毫秒到几秒,所以这是个同步阻塞 调用,官方明确要求不要在主线程调用,否则会卡 UI。

跨进程的桥 · ICarProperty.aidl

文件:car-lib/src/android/car/hardware/property/ICarProperty.aidl:41

aidl 复制代码
interface ICarProperty {
    void setProperty(in CarPropertyValue prop, in ICarPropertyEventListener callback) = 4;
    CarPropertyValue getProperty(int prop, int zone) = 3;
    void registerListener(int propId, float rate, in ICarPropertyEventListener callback) = 0;
    ...
}

AIDL 是 Android 的"跨进程协议书"。你在 App 进程调 mService.setProperty(), Binder 驱动会把参数序列化,搬到 CarService 进程,再调到服务端的同名方法。 App 这边是"代理(Proxy)",CarService 那边是"实现(Stub)"。

第 ③ 层 · CarPropertyService.setProperty()

文件:service/src/com/android/car/CarPropertyService.java:713

java 复制代码
public void setProperty(CarPropertyValue carPropertyValue,
        ICarPropertyEventListener listener) {
    // ① 校验:propertyId 合法吗?areaId 对吗?值的类型对吗?有写权限吗?
    // ② 记录"是谁设置的",方便出错时把错误回调给正确的客户端
    mPropertyHalService.setProperty(carPropertyValue);   // ③ 往 HAL 层下传
}

这一层是"门卫 + 调度员":

  • 校验权限(你这个 App 有没有资格控车窗?没有就抛 SecurityException)。
  • 维护一张"哪个客户端订阅了哪个属性"的表(mPropIdClientMap)。
  • 记录"刚才是谁设置的"(mSetOperationClientMap),这样万一设置失败,能精准地告诉那个 App。
  • 真正的活儿交给下一层 PropertyHalService

第 ③.5 层 · PropertyHalService.setProperty()

文件:service/src/com/android/car/hal/PropertyHalService.java:1047

java 复制代码
public void setProperty(CarPropertyValue carPropertyValue) {
    HalPropValue valueToSet =
        carPropertyValueToHalPropValueLocked(carPropertyValue);  // ① 格式转换
    mVehicleHal.set(valueToSet);                                 // ② 交给 VehicleHal
}

这一层是"翻译官",干两件最关键的事:

  1. ID 转换 :上层 App 用的 propertyId(叫 manager prop id )和底层 VHAL 用的 propertyId(叫 hal prop id )有时不一样,这里做映射(halToManagerPropId / managerToHalPropId)。
  2. 数据结构转换 :把面向应用的 CarPropertyValue 转成面向硬件的 HalPropValuecarPropertyValueToHalPropValueLocked,行号 1701)。

为什么要两套数据结构? 上层 CarPropertyValue 是给 Java 应用用的,强类型、好用; 底层 HalPropValue 贴近 VHAL 的内存布局(int32 数组、float 数组、字节数组),是为了高效跨 HAL 传输。

第 ④ 层 · VehicleHal.set()

文件:service/src/com/android/car/hal/VehicleHal.java:816

java 复制代码
public void set(HalPropValue propValue) {
    mVehicleStub.set(propValue);   // 交给 VehicleStub
}

VehicleHal 是整个 CarService 里唯一 跟 VHAL 打交道的总管。它下面是 VehicleStub

第 ④.5 层 · VehicleStub / AidlVehicleStub

文件:service/src/com/android/car/AidlVehicleStub.java

VehicleStub 是个抽象层,屏蔽了 VHAL 是 AIDL 版 还是老的 HIDL 版的差异:

  • 新平台用 AidlVehicleStub(对接 AIDL HAL)
  • 老平台用 HidlVehicleStub

它在这里把 HalPropValue 打包成 SetValueRequests,然后第二次跨进程,调到真正的 VHAL 服务端。

第 ⑤ 层 · IVehicle.setValues() ------ 进入厂商地盘

文件:hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl:155

aidl 复制代码
interface IVehicle {
    void getValues(IVehicleCallback callback, in GetValueRequests requests);
    void setValues(IVehicleCallback callback, in SetValueRequests requests);
    void subscribe(in IVehicleCallback callback, in SubscribeOptions[] options,
                   int maxSharedMemoryFileCount);
    void unsubscribe(in IVehicleCallback callback, in int[] propIds);
    ...
}

到这里,请求离开了 Google 的框架代码,进入车厂/Tier1 自己写的 C++ VHAL 实现

scss 复制代码
IVehicle.setValues()  →  厂商 VHAL 解析请求
                      →  组一帧 CAN 报文 (或 LIN / 车载以太网)
                      →  发给 MCU / 车身控制器
                      →  车窗电机转动,玻璃下降 🪟⬇️

注意:setValues异步 的。VHAL 把命令发上车总线后就返回了, 真正"窗户降到底了"这个结果,是通过后面讲的事件上报通道再传回来的。

✅ 写方向完整链路

scss 复制代码
App
 └─ CarPropertyManager.setProperty()                      [car-lib]
     └─(Binder: ICarProperty)─ CarPropertyService.setProperty()   [CarService]
         └─ PropertyHalService.setProperty()              ←CarPropertyValue→HalPropValue
             └─ VehicleHal.set()
                 └─ VehicleStub/AidlVehicleStub.set()
                     └─(Binder: IVehicle)─ 厂商 VHAL.setValues()  [C++]
                         └─ CAN/LIN/以太网 → MCU → 车

4. 读方向:getProperty ------ "现在车速多少?"

读和写几乎对称,只是数据流反过来,而且读是立刻要结果

第 ② 层 · CarPropertyManager.getProperty()

文件:car-lib/src/android/car/hardware/property/CarPropertyManager.java:1863

java 复制代码
public <E> CarPropertyValue<E> getProperty(int propertyId, int areaId) {
    assertPropertyIdIsSupported(propertyId);
    CarPropertyValue<E> v = runSyncOperation(() -> {
        return mService.getProperty(propertyId, areaId);   // Binder → CarService
    });
    return v;
}

第 ③ 层 · CarPropertyService.getProperty()

文件:service/src/com/android/car/CarPropertyService.java:651

java 复制代码
public CarPropertyValue getProperty(int propertyId, int areaId) {
    // 校验 + 权限检查后:
    return mPropertyHalService.getProperty(propertyId, areaId);
}

第 ③.5 层 · PropertyHalService.getProperty()

文件:service/src/com/android/car/hal/PropertyHalService.java:960

java 复制代码
public CarPropertyValue getProperty(int mgrPropId, int areaId) {
    int halPropId = managerToHalPropId(mgrPropId);     // ID 转换
    HalPropValue halPropValue = mVehicleHal.get(halPropId, areaId);  // 向下读
    return halPropValue.toCarPropertyValue(mgrPropId, ...);          // 结果转回 App 格式
}

第 ④ / ⑤ 层 · VehicleHal.get() → IVehicle.getValues()

文件:service/src/com/android/car/hal/VehicleHal.java:689get(int, int))→ 内部 getValueWithRetrymVehicleStub.get() → 跨进程 IVehicle.getValues()

注意 getValueWithRetry(VehicleHal.java:322 附近)有自动重试 逻辑:如果车端返回 TRY_AGAIN(数据还没准备好),它会重试几次,省得 App 自己写循环。

✅ 读方向完整链路

scss 复制代码
App
 └─ CarPropertyManager.getProperty()
     └─(Binder)─ CarPropertyService.getProperty()
         └─ PropertyHalService.getProperty()   ←halPropValue.toCarPropertyValue()
             └─ VehicleHal.get() → getValueWithRetry()
                 └─ VehicleStub.get()
                     └─(Binder)─ 厂商 VHAL.getValues()

5. 订阅 + 事件上报:registerCallback ------ "车速一变就告诉我"

这是 CarProperty 最常用、也最容易绕晕的部分。它分两个阶段阶段 A 注册(自上而下)阶段 B 事件回传(自下而上)

阶段 A:注册订阅

第 ② 层 · CarPropertyManager.registerCallback()

文件:car-lib/src/android/car/hardware/property/CarPropertyManager.java:1193

java 复制代码
public boolean registerCallback(CarPropertyEventCallback callback,
        int propertyId, float updateRateHz) {
    // ① 取出该属性的配置,校验采样率是否合理
    float rate = InputSanitizationUtils.sanitizeUpdateRateHz(config, updateRateHz);
    // ② 每个 propertyId 用一个"控制器"管理它的所有回调
    CarPropertyEventCallbackController controller =
        mPropertyIdToCarPropertyEventCallbackController.get(propertyId);
    if (controller == null) {
        controller = new CarPropertyEventCallbackController(propertyId, mLock, ...);
    }
    controller.add(callback, rate);   // 把你的回调登记进去
    ...
}

关键角色 CarPropertyEventCallbackControllercar-lib/src/com/android/car/internal/CarPropertyEventCallbackController.java): 它在 App 进程内管理"同一个属性的多个回调",比如 A 模块和 B 模块都订阅了车速,事件来了它负责分发给两个回调,还能按各自的采样率过滤。

采样率 updateRateHz 是什么?

  • ON_CHANGE 属性(如车门开关):值变了才推,采样率无所谓。
  • CONTINUOUS 属性(如车速):持续变化,你可以要求"每秒最多给我 10 次",写 10f。 设太高费 CPU,设太低反应慢,按需取舍。
第 ③ 层 · CarPropertyService.registerListener()

文件:service/src/com/android/car/CarPropertyService.java:330

java 复制代码
public void registerListener(int propertyId, float updateRateHz,
        ICarPropertyEventListener listener) {
    // 把 (客户端 listener, 属性) 关系记进 mPropIdClientMap
    // 只有当新采样率比当前订阅的更高时,才真正下发订阅,避免重复
    if (sanitizedUpdateRateHz > mPropertyHalService.getSubscribedUpdateRateHz(propertyId)) {
        mPropertyHalService.subscribeProperty(propertyId, sanitizedUpdateRateHz);  // 行号 370
    }
}

精髓 :多个 App 订阅同一个属性,CarService 只会向底层下发一份 订阅(取最高采样率),事件来了再扇出给所有 App。这叫订阅聚合,省资源。

第 ③.5 / ④ / ⑤ 层 · 一路订阅到 VHAL
scss 复制代码
PropertyHalService.subscribeProperty()      [PropertyHalService.java:1063]
  └─ VehicleHal.subscribeProperty()          [VehicleHal.java:503/517]
      └─ VehicleStub.SubscriptionClient.subscribe()
          └─(Binder)─ IVehicle.subscribe()   [IVehicle.aidl:227]
              └─ 厂商 VHAL 开始监听这个信号

阶段 B:事件回传------ 真正的"信号链路"

车速变了,VHAL 怎么把这个消息送回到你的回调里?这是一条完全反向的路。

第 ⑤ → ④ 层 · VHAL 回调 onPropertyEvent

VHAL 通过当初注册的 IVehicleCallback,调用 onPropertyEvent,事件进入 CarService 进程:

文件:service/src/com/android/car/hal/VehicleHal.java:858

java 复制代码
public void onPropertyEvent(ArrayList<HalPropValue> propValues) {
    mHandler.post(() -> handleOnPropertyEvent(propValues));  // 切到 Handler 线程处理
}

为什么要 mHandler.post? 因为这是从 Binder 线程进来的,赶紧把活儿丢到自己的工作线程,别堵住 Binder 线程池。

第 ③.5 层 · PropertyHalService.onHalEvents()

文件:service/src/com/android/car/hal/PropertyHalService.java:1284

java 复制代码
public void onHalEvents(List<HalPropValue> halPropValues) {
    List<CarPropertyEvent> eventsToDispatch = new ArrayList<>();
    for (HalPropValue halPropValue : halPropValues) {
        int mgrPropId = halToManagerPropId(halPropId);              // ① ID 转回 App 用的
        CarPropertyValue<?> carPropertyValue =
            halPropValue.toCarPropertyValue(mgrPropId, halPropConfig);  // ② 转回 App 数据结构
        CarPropertyEvent event = new CarPropertyEvent(
            CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyValue);
        eventsToDispatch.add(event);
    }
    mPropertyHalListener.onPropertyChange(eventsToDispatch);        // ③ 上抛给 CarPropertyService
}

这里又做了一次"翻译",方向和写/读时相反:HalPropValueCarPropertyValue,并包装成 CarPropertyEvent(事件对象,区分"值变化"还是"出错")。

第 ③ 层 · CarPropertyService.onPropertyChange()

文件:service/src/com/android/car/CarPropertyService.java:776

java 复制代码
public void onPropertyChange(List<CarPropertyEvent> events) {
    Map<Client, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>();
    // ① 根据 mPropIdClientMap,算出"每个事件该发给哪些客户端"
    for (CarPropertyEvent event : events) {
        int propId = event.getCarPropertyValue().getPropertyId();
        for (Client c : mPropIdClientMap.get(propId)) {
            eventsToDispatch.get(c).add(event);
        }
    }
    // ② 注意:在锁外面调用回调,避免死锁
    for (Client client : eventsToDispatch.keySet()) {
        client.onEvent(eventsToDispatch.get(client));   // Binder 回 App 进程!
    }
}

这里是"扇出(fan-out)"的地方 :一个事件,发给所有订阅了它的 App。client.onEvent() 这一步又跨进程回到了你的 App。

源码注释专门强调:一定要在锁外面调 onEvent,因为回调里可能反过来调 CarPropertyService 的方法,在锁内调用会死锁。这是个很经典的并发坑。

第 ② 层 · 回到 App 进程:CarPropertyEventListenerToService

文件:car-lib/src/android/car/hardware/property/CarPropertyManager.java:1232

java 复制代码
private static class CarPropertyEventListenerToService
        extends ICarPropertyEventListener.Stub {
    @Override
    public void onEvent(List<CarPropertyEvent> events) {
        CarPropertyManager mgr = mCarPropertyManager.get();
        if (mgr != null) {
            mgr.handleEvents(events);     // → mHandler.sendEvents(events)
        }
    }
}

事件回到 App 进程后: handleEvents()mHandler.sendEvents() → 最终交给前面那个 CarPropertyEventCallbackController,由它分发给你注册的每一个 CarPropertyEventCallback.onChangeEvent(CarPropertyValue value)

到这里,你的 App 终于在回调里拿到了最新车速。 🎉

✅ 事件上报完整链路(这条最重要,建议背熟)
scss 复制代码
车身网络 (CAN) 值变化
 └─ 厂商 VHAL 检测到变化
     └─(Binder: IVehicleCallback)─ VehicleHal.onPropertyEvent()      [VehicleHal.java:858]
         └─ handleOnPropertyEvent → PropertyHalService.onHalEvents()  [1284]
             └─ HalPropValue→CarPropertyValue,包成 CarPropertyEvent
                 └─ CarPropertyService.onPropertyChange()             [776]  ←按属性扇出给多个客户端
                     └─(Binder: ICarPropertyEventListener)─ CarPropertyManager内部.onEvent() [1232]
                         └─ handleEvents → Handler → CarPropertyEventCallbackController
                             └─ 你的 CarPropertyEventCallback.onChangeEvent()  ✅

6. 错误是怎么传回来的?(onPropertySetError)

set 是异步的,万一车端拒绝了(比如车速过高时不让开窗),错误怎么回来?

  1. VHAL 通过 IVehicleCallback.onPropertySetError 上报 → VehicleHal.onPropertySetError()VehicleHal.java:862
  2. PropertyHalService.onPropertySetError()PropertyHalService.java:1371
  3. CarPropertyService.onPropertySetError()CarPropertyService.java:821) 这里用之前记的 mSetOperationClientMap 找到当初是谁 set 的
  4. → 只把错误事件(PROPERTY_EVENT_ERROR)回调给那一个 App。

这就是为什么 CarService 要费劲记录"谁设置的"------精准报错,不打扰无关的 App。


7. 异步读写

除了同步的 getProperty/setProperty,还有批量异步接口:

  • CarPropertyManager.getPropertiesAsync() / setPropertiesAsync()
  • ICarProperty.getPropertiesAsync()(AIDL,行号 = 8/10)
  • CarPropertyServicePropertyHalService.getCarPropertyValuesAsync()(行号 873/915 附近)
  • VehicleHal.getAsync()/setAsync()VehicleHal.java:1394/1402
  • VehicleStub.getAsync()/setAsync()

适合"一次读几十个属性"的场景,不阻塞线程,通过 IAsyncPropertyResultCallback 回结果。日常开发用同步接口就够了。


8. 关键类速查表

文件 职责一句话
② SDK CarPropertyManager car-lib/.../property/CarPropertyManager.java App 的入口,get/set/registerCallback
② SDK CarPropertyEventCallbackController car-lib/.../internal/CarPropertyEventCallbackController.java App 进程内分发回调、按采样率过滤
ICarProperty.aidl car-lib/.../property/ICarProperty.aidl App↔CarService 的跨进程协议
③ Service CarPropertyService service/.../CarPropertyService.java 权限校验、客户端管理、事件扇出
③ Service PropertyHalService service/.../hal/PropertyHalService.java ID 转换、CarPropertyValue↔HalPropValue
④ HAL胶水 VehicleHal service/.../hal/VehicleHal.java 跟 VHAL 通信的总管、自动重试
④ HAL胶水 VehicleStub/AidlVehicleStub service/.../AidlVehicleStub.java 屏蔽 AIDL/HIDL 差异
IVehicle.aidl hardware/interfaces/automotive/vehicle/.../IVehicle.aidl CarService↔VHAL 的跨进程协议
⑤ VHAL 厂商 C++ 实现 vendor 厂商代码 解析请求、收发 CAN/LIN 报文

9. 数据结构对照

用在哪 类名 长相 一句话
App 层 CarPropertyValue propertyId + areaId + value(强类型) + status + timestamp 给 Java 应用用的,好用
App 层 CarPropertyEvent type(变化/出错) + CarPropertyValue 事件信封,区分正常变化和错误
App 层 CarPropertyConfig 属性的"说明书":类型、区域、采样率范围、读写权限 getPropertyList 拿到的就是它
HAL 层 HalPropValue int32\[\]/float\[\]/byte\[\] 等裸数组 贴近硬件、跨 HAL 高效传输
HAL 层 HalPropConfig VHAL 侧的属性配置 和 CarPropertyConfig 互转

转换发生在 PropertyHalService

  • 下行 carPropertyValueToHalPropValueLocked()(PropertyHalService.java:1701)
  • 上行 HalPropValue.toCarPropertyValue()

10. 一句话总结全文

App 调 CarPropertyManager → 跨进程到 CarPropertyService(管权限和客户端)→ PropertyHalService(管翻译)→ VehicleHal/VehicleStub → 再跨进程到厂商 IVehicle 实现 → CAN 总线 → 车。 读、写、订阅走的是同一条路,只是方向和同步/异步不同;事件上报则是这条路完整地倒着走一遍。

把这条链路在脑子里跑通,以后无论是"App 读不到信号"、"set 不生效"、"订阅没回调",你都能快速定位是哪一层断了:

  • App 收不到回调 → 看 CarPropertyService.onPropertyChange 有没有进来、mPropIdClientMap 里有没有你。
  • 值一直是默认值 → 看 PropertyHalService.onHalEvents 是否被丢弃(unsupported property / payload check 失败)。
  • set 报权限错 → 卡在 CarPropertyService 的权限校验。
  • 底层根本没动 → 看 VehicleHal/VehicleStub 有没有把请求发到 IVehicle

相关推荐
敲代码的鱼1 小时前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
时光足迹3 小时前
uni-app 视频通话实战:康复师与患者视频问诊的 6 个致命 Bug 与解决方案
android·ios·uni-app
Coffeeee7 小时前
闲聊几句,Android老哥们,你们多久没做技改需求了
android·程序员·代码规范
萝卜er8 小时前
Fragment 生命周期与状态恢复-《Android深水区(四)》
android
萝卜er8 小时前
Intent 显式、隐式与 PendingIntent-《Android深水区(五)》
android
Kapaseker10 小时前
一文吃透 Kotlin 集合操作符
android·kotlin
三少爷的鞋11 小时前
Main-safe:现代Android 架构真正的分水岭
android
沐怡旸20 小时前
深入解析 Android Performance Analyzer (APA) 底层架构与技术原理
android
李斯维1 天前
从历史的角度看 Android 软件架构
android·架构·android jetpack