3、项目结构与基础组件:Automotive项目模板、AndroidManifest.xml配置、Application与Activity、Service与BroadcastReceiver

好,咱们今天聊聊 Automotive 项目的骨架和血肉。说白了,就是当你新建一个车载项目时,Android Studio 给你生成了什么,以及这些文件到底怎么用。

我记得第一次接触 Automotive 项目时,打开工程一看,好家伙,跟普通手机项目长得差不多嘛。但实际跑起来才发现——坑都在细节里。今天我就把这些细节掰开揉碎了讲给你听。

3.1 Automotive 项目模板长什么样?

Android Studio 里创建 Automotive 项目时,你会看到一个专门的模板选项。它跟普通手机模板最大的区别在哪?我直接说结论:多了一个 car 模块

典型的项目结构是这样的:

MyCarApp/
├── app/                    # 主应用模块(跟手机一样)
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   ├── res/
│   │   │   └── AndroidManifest.xml
│   └── build.gradle
├── car/                    # Automotive 专属模块
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   ├── res/
│   │   │   └── AndroidManifest.xml
│   └── build.gradle
├── build.gradle            # 根级构建文件
└── settings.gradle

你可能会问:为什么非要拆成两个模块?

嗯,这里有个设计哲学。app 模块跑在手机或平板上,car 模块跑在车机上。两个模块可以共享代码,但各自有独立的 Manifest 和资源。我在项目中就遇到过这种情况——同一个功能,手机端和车机端的 UI 布局完全不同,拆开管理反而清爽。

核心要点:car 模块的 build.gradle 里必须声明 android.car 插件,否则系统不认你是车载应用。

3.2 AndroidManifest.xml 配置——别小看这几行

车载应用的 Manifest 跟普通应用比,多了几个关键配置。我直接给你看一个典型的 car 模块 Manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mycarapp.car">

    <uses-feature
        android:name="android.hardware.type.automotive"
        android:required="true" />

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Car">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyCarService"
            android:exported="true"
            android:permission="android.permission.BIND_CAR_SERVICE" />

        <receiver
            android:name=".MyCarReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.car.intent.action.CAR_HEAD_UNIT" />
            </intent-filter>
        </receiver>

    </application>
</manifest>

注意看这行:android.hardware.type.automotive。这个 feature 声明告诉 Google Play:我的应用只能在车机上安装。如果你不加,应用有可能被安装到手机上,到时候各种崩溃别怪我没提醒你。

我曾经见过一个团队,忘了加这个 feature,结果测试机死活装不上。查了半天才发现是 Manifest 的问题。嗯,这种低级错误,犯一次就记住了。

3.3 Application 与 Activity——车载环境下的特殊要求

车载应用的 Application 类,其实跟手机端差别不大。但 Activity 就完全不一样了。

你想想看,司机在开车时能像玩手机一样点来点去吗?显然不行。所以 Android Automotive 对 Activity 有严格的限制:

  • 禁止横竖屏切换:车机屏幕通常是固定的 landscape 模式
  • 禁止多窗口:车载系统里,你的 Activity 要么全屏,要么被系统接管
  • 必须处理驾驶状态:车辆行驶时,某些操作会被系统拦截

我个人的习惯是,在 Application 的 onCreate() 里提前初始化 Car API:

public class MyCarApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化 Car 连接
        Car car = Car.createCar(this);
        // 注册驾驶状态监听
        car.getCarManager(Car.PROPERTY_SERVICE);
    }
}

至于 Activity,我建议你继承 CarActivity 而不是 AppCompatActivity。为什么?因为 CarActivity 已经帮你处理了驾驶模式下的 UI 限制。我自己踩过这个坑——直接用 AppCompatActivity,结果在模拟器上跑得好好的,真机上一开车就闪退。

3.4 Service——车载应用的后台心脏

车载 Service 跟普通 Service 最大的区别在于:它需要绑定到 Car 服务上

举个例子,你要监听车辆的速度、转向灯状态,这些数据都来自 Car API。而 Car API 本身是一个系统服务,你的 Service 必须通过绑定才能拿到数据。

看一个典型的车载 Service 实现:

public class MyCarService extends Service {

    private Car mCar;
    private CarPropertyManager mPropertyManager;

    @Override
    public void onCreate() {
        super.onCreate();
        mCar = Car.createCar(this, new Car.CarServiceLifecycleListener() {
            @Override
            public void onConnected(Car car) {
                mPropertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
                // 开始监听车辆属性
                mPropertyManager.registerCallback(mCallback, 
                    VehiclePropertyIds.PERF_VEHICLE_SPEED, 0);
            }

            @Override
            public void onDisconnected(Car car) {
                // 清理资源
            }
        });
        mCar.connect();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mCar != null) {
            mCar.disconnect();
        }
    }
}

这里有个关键点:Car.createCar() 必须在主线程调用。我刚开始做的时候,傻乎乎地在子线程里创建,结果回调永远不触发。查了两天文档才发现这个限制。

小技巧:如果你的 Service 需要长时间运行,记得在 Manifest 里声明 android:foregroundServiceType="dataSync",否则 Android 12 以上会直接杀掉你的进程。

3.5 BroadcastReceiver——系统事件的耳朵

车载系统里,BroadcastReceiver 的用途比手机端更广。除了常见的网络变化、电量变化,你还需要监听:

  • 驾驶模式切换:停车 vs 行驶,UI 行为完全不同
  • 车辆电源状态:ACC 开启/关闭,决定你的应用是否继续运行
  • 车载系统广播:比如 android.car.intent.action.CAR_HEAD_UNIT

我建议你把 BroadcastReceiver 注册在 Manifest 里,而不是动态注册。为什么?因为车载系统启动时,你的应用可能还没运行,但系统广播已经发出了。静态注册能保证你的 Receiver 被系统唤醒。

看一个监听车辆电源状态的例子:

public class PowerStateReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Car.CAR_ACTION_POWER_STATE_CHANGED.equals(action)) {
            int powerState = intent.getIntExtra(Car.CAR_EXTRA_POWER_STATE, 
                Car.POWER_STATE_UNDEFINED);
            switch (powerState) {
                case Car.POWER_STATE_ON:
                    // 车辆通电,可以启动服务
                    break;
                case Car.POWER_STATE_OFF:
                    // 车辆断电,保存数据并退出
                    break;
            }
        }
    }
}

注意:车载 BroadcastReceiver 的 onReceive() 方法运行在主线程。如果你在里面做耗时操作,比如写数据库,系统会直接 ANR。我建议你收到广播后,启动一个 IntentService 来处理具体逻辑。

3.6 知识体系总览

下面这张图,把本章的核心内容串起来了。你一看就明白各个组件之间的关系:

Android Automotive 项目结构与基础组件 项目模板 app模块 + car模块 AndroidManifest.xml automotive feature声明 Application 初始化Car API Activity 继承CarActivity Service 绑定Car服务 BroadcastReceiver 监听系统事件 核心关系 项目模板定义结构 → Manifest声明组件 → Application初始化环境 Activity处理UI → Service处理后台 → BroadcastReceiver响应事件

这张图里,从上到下展示了 Automotive 项目的完整链路。你写代码时,就按这个顺序来:先搭模板,再配 Manifest,然后实现 Application、Activity、Service 和 BroadcastReceiver。每一步都有它的职责,缺一不可。

好了,这一章的内容就这些。记住一句话:车载应用不是手机应用的简单移植,它有自己的生命周期和约束。你把这些基础组件吃透了,后面学 Car API 和 HMI 开发就会轻松很多。


公众号:蓝海资料掘金营,微信deep3321