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 知识体系总览
下面这张图,把本章的核心内容串起来了。你一看就明白各个组件之间的关系:
这张图里,从上到下展示了 Automotive 项目的完整链路。你写代码时,就按这个顺序来:先搭模板,再配 Manifest,然后实现 Application、Activity、Service 和 BroadcastReceiver。每一步都有它的职责,缺一不可。
好了,这一章的内容就这些。记住一句话:车载应用不是手机应用的简单移植,它有自己的生命周期和约束。你把这些基础组件吃透了,后面学 Car API 和 HMI 开发就会轻松很多。