Android车载开发启示录(二)

笔者在从事Android车载行业的开发过程中,发现Android车载开发和平时的Android开发还是有很大不同之处,对于一个小白来说或者说如果是刚入行的新人都会很陌生,目前市场也没有很多系统性的知识提供给大家。

所以笔者准备通过一个专栏系列,把自己在车载开发过程中的学习记录和开发经验记录下来并分享出来,希望能给大家带来一些帮助。

在第一篇内容,笔者介绍了Android车载操作系统现状、整个操作系统架构和架构下核心概念:

Android车载开发启示录(一)

本篇内容会落到和实际应用开发直接相关的CarFrameworkCarFramework在Android Automotive操作系统中扮演着类似于Android框架在智能手机上的角色。

它是Android Automotive操作系统中的一个关键组件,提供了与车辆系统交互的基础设施和功能。CarFramework为车载应用程序提供统一的开发和执行环境,以便它们可以与车辆的硬件和软件进行交互。

主要内容如下:

  • Android Automotive、Android 和 Android Auto的关系和区别
  • Android Automotive的架构
  • CarService的启动流程
  • CarAPI、CarAPP

Android Automotive

看看Google是怎么定义Android Automotive

Android Automotive is a full-stack, open source, highly customizable platform running directly on in-vehicle hardware.

Android Automotive 是一个能直接在车载硬件上运行的全栈、开源、高度可定制的平台。

所以很重要的一点, Android Automotive 是一个基础平台。

这个平台可以运行预装在 IVI (in-vehicle infotainment)系统的 Android 应用程序以及三方Android 应用程序。

由于是开源的,可以通过在免费开源代码库中提供基本的汽车信息娱乐功能来提高效率。它的开放性,使开发者可以根据根据需求来定制化产品。

既然是一个平台,那和 Android平台有什么区别,是在Android之外的一个分叉开发吗?Android Automotive 与整个Android生态系统是个什么关系?

Android Automotive & Android

Android Automotive 本质上还是 Android

Android Automotive 不是 Android 的分叉或并行开发。它与手机、平板电脑等设备上的 Android 具有相同的代码库和存储库。它构建在经过 10 多年开发的强大平台和功能集之上,使其能够利用现有的安全模型、兼容性程序、开发人员工具和基础设施,同时继续高度可定制和可移植,完全免费和开源。

Android Automotive 是对Android进行了扩展。在将Android构建为功能齐全的信息娱乐平台的过程中,添加了对汽车特定要求、功能和技术的支持。

Android Automotive & Android Auto

Android AutomotiveAndroid Auto 这两个词放在一起就容易晕,混淆不清。其实它们的区别还是很大的:

Android Automotive 上面说过,它本质上是Android平台,是需要在车载硬件上运行的。

Android Auto 是一个在用户手机上运行的平台,它的作用是:

通过 USB / 无线连接将手机上的部分应用投射到兼容的车载信息娱乐系统。Android Auto 支持专为车载使用而设计的应用程序。它和苹果手机上的CarPlay属于同类平台。

Android Automotive架构

先看下Google给出了车载HAL与Android Automotive架构:

  • Car APP: 上层应用,包含系统APP、OEM APP、三方的App
  • Car API :提供给上层应用的功能API,比如多媒体、仪表盘、空调、导航等。内有包含CarSensorManager在内的API。代码包路径(/platform/packages/services/Car/car-lib)
  • CarService :系统中与车相关的一系列服务,主要是基于CarProperty实现Vechile相关的一些策略。代码包路径(/platform/packages/services/Car/)
  • VehicleHAL:汽车的硬件抽象层描述。用于定义 OEM 可以实现的车辆属性的接口。包含属性元数据。例如,车辆属性是否为 int 以及允许使用哪些更改模式。代码包路径(hardware/interfaces/automotive/vehicle/2.0/default/)

车载HALAndroid Automotive架构可以这么理解:

Android Automotive场景提供了一系列的服务,这些服务统被称为CarService。它们是衔接上下层的桥梁。

往下层看:CarServiceHAL层的VehicleHAL通信,进而通过车载总线(例如CAN总线)与车身进行通讯; 往上层看:CarService为应用层的APP提供接口Car API,从而让APP能够实现对车身的控制与状态的显示。

所以,接下来介绍下这个架构中的"显眼包" - CarService

CarService

目录结构

原生的CarService业务代码庞大,包含了许多与汽车相关的服务,它的一级目录代码路径位于:

/platform/packages/services/Car/

目录结构是这样的:

arduino 复制代码
.
├── Android.mk
├── apicheck.mk
├── apicheck_msg_current.txt
├── apicheck_msg_last.txt
├── car-cluster-logging-renderer    //LoggingClusterRenderingService继承InstrumentClusterRenderingService
├── car-default-input-service   //按键消息处理
├── car-lib         //提供给汽车App特有的接口,许多定制的模块都在这里实现,包括Sensor,HVAC,Cabin,ActiveParkingAssiance,Diagnostic,Vendor等
├── car-maps-placeholder    //地图软件相关
├── car_product         //系统编译相关
├── car-support-lib     //android.support.car
├── car-systemtest-lib  //系统测试相关
├── car-usb-handler     //开机自启,用于管理车机USB
├── CleanSpec.mk
├── evs  
├── obd2-lib
├── PREUPLOAD.cfg
├── procfs-inspector
├── service    //com.android.car是一个后台运行的组件,可以长时间运行并且不需要和用户去交互的,这里即使应用被销毁,它也可以正常工作
├── tests
├── tools   //是一系列的工具,要提到的是里面的emulator,测试需要用到的。python写的,通过adb可以连接vehicleHal的工具,用于模拟测试
├── TrustAgent
└── vehicle-hal-support-lib

启动流程

CarService是Android中的服务组件,所以必须要了解下CarService的启动流程:

1.启动CarServiceHelperService

系统服务SystemServer启动时,会启动CarServiceHelperService服务:

SystemServe: main() -> run() ----> startOtherServices()

scss 复制代码
// frameworks/base/services/java/com/android/server/SystemServer.java

public static void main(String[] args) {
        new SystemServer().run();
}

private void run() {
  ...
  startBootstrapServices(t);
  startCoreServices(t);
  startOtherServices(t); //⭐️
  startApexServices(t);
  ...
}

// 启动CarServiceHelperService服务
private static final String CAR_SERVICE_HELPER_SERVICE_CLASS =
            "com.android.internal.car.CarServiceHelperService";
            
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
   boolean isAutomotive = mPackageManage
   .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
  if (isAutomotive) {
    final SystemService cshs = mSystemServiceManager
        .startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
    }
 }

2.启动CarService

CarServiceHelperServiceonStart方法最终会通过bindService的方式启动CarService

-> CarServiceHelperService.java: onStart() -> CarServiceHelperServiceUpdatableImpl: onStart() -> bindService() -> CarService.java: onCreate() -> CarServiceImpl.java: onCreate()

CarServiceHelperService#onStart()

启动CarServiceHelperService服务时,最终会调用到CarServiceHelperServiceonStart方法里

onStart方法最终会通过绑定服务的方式启动服务,这个服务就是CarService

其中绑定service对应的包是:"com.android.car",对应的action是:"android.car.ICar"

java 复制代码
//frameworks/opt/car/services/builtInServices/src/com/android/internal/car/CarServiceHelperService.java

public class CarServiceHelperService extends SystemService
        implements Dumpable, DevicePolicySafetyChecker, CarServiceHelperInterface {
        ...
        mCarServiceHelperServiceUpdatable.onStart();
}

public static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
private static final String CAR_SERVICE_PACKAGE = "com.android.car";
public final class CarServiceHelperServiceUpdatableImpl
        implements CarServiceHelperServiceUpdatable, Executor {
    @Override
    public void onStart() {
        // 设置action(CAR_SERVICE_INTERFACE)和package(CAR_SERVICE_PACKAGE)
        Intent intent = new Intent(CAR_SERVICE_INTERFACE).setPackage(CAR_SERVICE_PACKAGE);
        Context userContext = mContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0);
        if (!userContext.bindService(intent, Context.BIND_AUTO_CREATE, this,
                mCarServiceConnection)) {
            Slogf.wtf(TAG, "cannot start car service");
        }
    }
}
bindService()

通过bindService方式来启动Service会经过如下阶段:

scss 复制代码
context.bindService() ------> onCreate() ------> onBind() ------> Service running 

bindService后,最终会通过CarServiceImpl 去创建ICarImpl对象

scala 复制代码
/** Proxy service for CarServciceImpl */
public class CarService extends ServiceProxy {
    private  static final int MAX_BINDER_THREADS = 31;

    public static final String CAR_SERVICE_IMPL_CLASS = "com.android.car.CarServiceImpl";
    
    public CarService() {
        super(UpdatablePackageDependency.CAR_SERVICE_IMPL_CLASS);
        // Increase the number of binder threads in car service
        BinderInternal.setMaxThreads(MAX_BINDER_THREADS);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // keep it alive.
        return START_STICKY;
    }
}


public class ServiceProxy extends Service {

    private final String mRealServiceClassName;

    private UpdatablePackageContext mUpdatablePackageContext;
    private Class mRealServiceClass;
    private ProxiedService mRealService;

    public ServiceProxy(String realServiceClassName) {
        mRealServiceClassName = realServiceClassName;
    }

    @Override
    public void onCreate() {
        init(); // ⭐️
        mRealService.onCreate();
    }

	// ⭐️
    private void init() {
        mUpdatablePackageContext = UpdatablePackageContext.create(this);
        try {
            mRealServiceClass = mUpdatablePackageContext.getClassLoader().loadClass(
                    mRealServiceClassName);
            Constructor constructor = mRealServiceClass.getConstructor();
            mRealService = (ProxiedService) constructor.newInstance();
            mRealService.doAttachBaseContext(mUpdatablePackageContext);
            mRealService.setBuiltinPackageContext(this);
        } catch (Exception e) {
            throw new RuntimeException("Cannot load class:" + mRealServiceClassName, e);
        }
    }

}

public class CarServiceImpl extends ProxiedService {
      @Override
    public void onCreate() {
        ...
        mICarImpl = new ICarImpl(this,
                getBuiltinPackageContext(),
                mVehicle,
                SystemInterface.Builder.defaultSystemInterface(this).build(),
                mVehicleInterfaceName);
        mICarImpl.init();
        ...
        super.onCreate();
        ...
    }
}

ICarImpl 的构造函数会添加很多核心服务到集合列表中,然后在init方法中去初始化这些的服务

scss 复制代码
   void ICarImpl(Context serviceContext, ...){
        List<CarServiceBase> allServices = new ArrayList<>();
        allServices.add(mSystemActivityMonitoringService);
        allServices.add(mCarPowerManagementService);
        allServices.add(mCarPropertyService);
        allServices.add(mCarDrivingStateService);
        allServices.add(mCarUXRestrictionsService);
        ....
        mAllServices = allServices.toArray(new CarSystemService[allServices.size()]);
   }
   
   void init() {
        if (!mDoPriorityInitInConstruction) {
            priorityInit();
        }
        for (CarSystemService service : mAllServices) {
            t.traceBegin(service.getClass().getSimpleName());
            service.init();  // ⭐️
            t.traceEnd();
        }
        mCarOemService.onInitComplete();
    }

3.建立双向通信

CarServiceImpl类中的onBind会将该ICarImpl对象返回给CarServiceHelperService

scala 复制代码
public class CarServiceImpl extends ProxiedService {
    
    @Override
    public IBinder onBind(Intent intent) {
        return mICarImpl;
    }
}

mICarImpl会作为IBinder返回给CarServiceHelperService类的bindServiceAsUser方法中的参数mCarServiceConnection,此时CarServiceHelperService就拿到了CarServicebinder对象,可以调用接口方法,进行通信了。

scss 复制代码
#frameworks/opt/car/services/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java 

final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();

   private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            handleCarServiceConnection(iBinder); // ⭐️
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            handleCarServiceCrash();
        }
    };
    
   void handleCarServiceConnection(IBinder iBinder) {
        synchronized (mLock) {
            mCarServiceBinder = ICar.Stub.asInterface(iBinder); // ⭐️
        }
        ...
        sendSetSystemServerConnectionsCall(); // ⭐️
    }

CarServiceHelperService拿到该binder对象后的第一件事,就把自己的binder句柄mHelper,通过setSystemServerConnections方法发送给了CarService

arduino 复制代码
    private void sendSetSystemServerConnectionsCall() {
        ICar binder;
        synchronized (mLock) {
            binder = mCarServiceBinder;
        }
        ...
            binder.setSystemServerConnections(mHelper, mCarServiceConnectedCallback); // ⭐️ 
            ...
    }


# packages/services/Car/service/src/com/android/car/ICarImpl.java
public final class CarServiceHelperWrapper {
    public void setSystemServerConnections(ICarServiceHelper carServiceHelper,
            ICarResultReceiver resultReceiver) {
        ...
        mCarServiceHelperWrapper.setCarServiceHelper(carServiceHelper);
         ...
    }
}

此时,CarServiceCarServiceHelperService就成功建立起了双向跨进程通信。

最后小结下整个过程:

1.SystemServer启动CarServiceHelperService服务,在调用startService后,CarServiceHelperServiceonStart方法通过bindService的方式启动CarService 2.启动CarService后首先调用onCreate,创建ICarImpl对象并初始化,在此时创建了一系列car相关的核心服务,遍历并通过init进行初始化 3.然后在调用CarServiceonBind将该ICarImpl对象返回给CarServiceHelperService,然后CarServiceHelperService在内部的一个Binder对象ICarServiceHelperImpl传递给CarService`,建立双向跨进程。

service源码路径:packages/services/Car/service

Car API

前文提到过Car API在架构中的位置和作用::

CarService为应用层的APP提供接口Car API,从而让APP能够实现对车身的控制与状态的显示。

该源码目录包含汽车服务 API: 这些API 还作为 Android SDK 的一部分发布到最终的 Android Automotive OS SDK。所有供应商或应用程序代码可以使用此处定义的 API。 源码位置

由于Car API 仅用于开发汽车,所以并没有包含在Framework SDK中,如果需要使用,需要引入"android.car"的java库。

类图

下面是它的类图:

  • menu:车辆应用菜单相关API。
  • cluster:仪表盘相关API。
  • render:渲染相关API。
  • pm:应用包相关API。
  • diagnostic:包含与汽车诊断相关的API。
  • hardware:车辆硬件相关API。
  • cabin:座舱相关API。
  • hvac:通风空调相关API。
  • property:属性相关API(实现定制的property)。
  • radio:收音机相关API。
  • input:输入相关API。
  • media:多媒体相关API。
  • navigation:导航相关API
  • settings:设置相关API
  • vms:汽车监测相关API

关于Car API的使用,参考官方的的API文档就行了:Car API文档

CarApp

Car App层是与应用开发直接相关的,通过调用Car API,实现对车身的控制与状态的显示。比如空调、导航、多媒体功能。

判断车载功能支持

APP层在调用Car API之前首先会判断该平台是否支持车载功能

less 复制代码
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
   xxx
}

创建Car实例

通过createCar方法可以新建一个Car实例:

API 29之前可以使用serviceConnectionListener来创建Car对象

java 复制代码
public static Car createCar (Context context, 
                ServiceConnection serviceConnectionListener)

但这个方法在API 29就过时了,之后可以使用 CarServiceLifecycleListener 创建新的 Car 对象。 在释放传递的 {code Context} 之前,应通过调用disconnect() 来断开用此创建的实例与汽车服务的连接。

java 复制代码
public static Car createCar (Context context, 
                Handler handler, 
                long waitTimeoutMs, 
                Car.CarServiceLifecycleListener statusChangeListener)

此调用最多可阻塞指定的 waitTimeoutMs,以等待汽车服务准备就绪。 如果汽车服务在给定时间内没有准备好,它将返回一个处于断开状态的 Car 实例。 永远阻塞主线程可能会导致系统出现 ANR(应用程序无响应)终止,如果应用程序应该在汽车服务崩溃/重新启动时继续存在,则不应使用。 如果汽车服务尚未准备好,应用程序无法执行任何操作,它仍然很有用。 在任何等待过程中,如果线程被中断,它将立即返回。

建立连接

如果是通过API 29之前ServiceConnection的方式来创建的Car对象,就需要通过connect方法连接CarService

csharp 复制代码
public void connect ()

如果是通过createCar(android.content.Context, android.os.Handler)的方式,就不需要调用了,也就是说这个API 在29就过时了。

获取Manager

当成功连接时可以通过getCarManager方法获取获取汽车特定服务,拿到一个一个相关的manager

typescript 复制代码
public Object getCarManager (String serviceName)

例如要获取传感器服务,就可以通过服务名Car.SENSOR_SERVICE获取相应的Manager去操作了:

ini 复制代码
SensorManagerServicesensorManagerService = car.getCarManager(Car.SENSOR_SERVICE);

CarService具体业务

CarService业务量非常大,包含了许多与汽车相关的服务,由于使用比较简单,笔者不打算详细介绍。 但笔者找到介绍一些常用汽车服务的文章,写的还是比较详细的,想了解使用细节的可以参考:

CarPropertyService

绝大部分与车辆硬件功能相关联的属性,如空调, 车舱功能, 车辆传感器等都是通过CarPropertyService来读取或者设置的,具体如何使用的,可以参考这两篇博客,写的比较详细:

Android汽车服务篇(二) CarPropertyService上篇

Android汽车服务篇(三) CarPropertyService下篇

CarMediaService

在车载上,音频设备的数量还是使用场景都和手机有很大的不同,紧靠Android原有的音频服务是无法满足在车内的使用需求的。 因此AAOS对Android原有的音频机制进行了扩充. 在CarService中加入了CarAudioService.对音频设备进行更加细致的管理,以满足车上的使用场景。

Android汽车服务篇(四) CarAudioService

CarDrivingStateService

在UX Restrictions中需要根据当前的车辆行驶状态,决定当前的限制规则. 其中行驶状态地获取就是通过CarDrivingStateService来实现的. 它的主要职责就是对外提供车辆的行驶状态信息。应用可以使用CarDrivingStateManager获取和监听驾车状态。

Android汽车服务篇(五) CarDrivingStateService

CarPackageManagerService

主要用于车上使用场景扩充了一些包管理相关的接口. 包括黑白名单的机制. 这主要是出于安全的考虑, 车上的应用有更严格的限制. 结合用户体验限制对运行在AAOS上的应用有一个更好的约束。

Android汽车服务篇(六) CarPackageManagerService

CarPowerManagementService

汽车电源管理服务。电源管理是AAOS上又一个比较特殊的部分. 由于车辆的使用场景的特殊性和复杂性, 同时需要和其他ECU(Electronic Control Unit)电子控制单元的配合, 都增加了车载系统电源管理的难度。

Android汽车服务篇(七) CarPowerManagementService

概念解释

  • IVI system:in-vehicle infotainment system,车载信息娱乐系统

车载信息娱乐是采用车载专用中央处理器,基于车身总线系统和互联网服务,形成的车载综合信息处理系统。IVI能够实现包括三维导航、实时路况、IPTV、辅助驾驶、故障检测、车辆信息、车身控制、移动办公、无线通讯、基于在线的娱乐功能及TSP服务等一系列应用。

  • ECU:Electronic Control Unit,电子控制单元

主要作用:提供信号的输入/输出接口,接收信号、处理信号、输出信号

  • HAL:Hardware Abstraction Layer,硬件抽象层

使用硬件抽象,而不是直接与硬件设备通信的程序,它将程序传达给操作系统该设备应执行的操作,然后,操作系统会向该设备生成硬件相关的指令。这意味着程序员不需要知道特定设备的工作方式,就能使他们的程序与设备兼容。

HAL的意义有以下两个方面:

  1. HAL层屏蔽掉不同硬件设备的差异,为Android提供了统一的设备访问接口。不同的硬件厂商遵循HAL标准来实现自己的硬件控制逻辑,开发者不必关心硬件设备的差异,只需按照HAL提供的标准接口对硬件进行访问即可。

  2. HAL层帮助硬件厂商隐藏了设备的核心细节,HAL层位于用户空间,遵循Apache协议,允许硬件厂商不公开源码,将设备相关的实现放在Android系统中HAL具有两种实现方式:Legacy以及Stub HAL,初期使用的是Legacy HAL的方式,该方式为标准的Linux共享库,其它应用程序直接调用HAL层共享库导出的函数。Google后来提出了Stub HAL的方式,仍然以共享库(.so)的形式提供,它把所有供外部访问的的方法(函数)的入口指针保存在统一的数据结构,其它程序需要访问HAL中方法时,需要先获得Stub,然后通过具体的函数指针去读写底层设备。

参考

博客参考如下文章,想深入了解具体知识点的也可以自取参考学习:

Android CarFrameWork

www.jianshu.com/p/7463f0b1d...

Android Automotive之Car API

blog.csdn.net/qq_34211365...

HAL抽象层的原理

juejin.cn/post/720038...

相关推荐
前端切图仔0014 分钟前
Chrome 扩展程序上架指南
android·java·javascript·google
黄林晴6 分钟前
Compose Multiplatform 1.10.0 重磅发布!三大核心升级,跨平台开发效率再提升
android·android jetpack
锁我喉是吧8 分钟前
Android studio 编译faiss
android·android studio·faiss
鹏程十八少12 分钟前
3. Android 腾讯开源的 Shadow,凭什么成为插件化“终极方案”?
android·前端·面试
TheNextByte114 分钟前
如何通过蓝牙将联系人从Android传输到 iPhone
android·cocoa·iphone
Wpa.wk17 分钟前
性能测试-性能监控相关命令-基础篇
android·linux·运维·经验分享·测试工具·性能测试·性能监控
Kapaseker18 分钟前
必须要搞懂的 View 核心问题
android·kotlin
西红市杰出青年18 分钟前
crawl4ai------AsyncPlaywrightCrawlerStrategy使用教程
开发语言·python·架构·正则表达式·pandas
Qiuner26 分钟前
一文读懂 Lambda
java·spring boot·后端·架构
东方佑26 分钟前
思维自指:LLM推理架构的维度突破与意识雏形
人工智能·架构