11、车载应用开发基础:Automotive应用架构与第一个车载应用

各位同学,欢迎来到第11章。从这一章开始,我们真正进入车载应用开发的核心地带。说实话,车载应用和手机应用虽然都叫“App”,但背后的设计思路、API体系、甚至UI规范,完全是两码事。我当年从手机端转到车载开发时,第一个感觉就是——这玩意儿怎么这么多规矩?

但等你理解了它的设计哲学,你会发现,这些“规矩”其实都是为了安全、稳定和高效。今天我们就从架构、API、UI规范,再到一个完整的车速显示应用,一步步走通。

11.1 Automotive应用架构:CarApp与CarActivity

先问大家一个问题:车载应用为什么不能直接用普通的Activity?

原因很简单——车载系统需要管理多窗口、多用户、驾驶安全优先级。普通的Activity生命周期在车载场景下根本不够用。Google为此专门设计了CarAppCarActivity这套架构。

11.1.1 CarApp——应用的“总管”

CarApp是一个抽象类,它管理着整个车载应用的生命周期。我个人习惯把它理解成“应用级别的管家”。它负责的事情包括:

  • 管理CarActivity的创建和销毁
  • 处理车载特有的生命周期事件(比如驾驶模式切换)
  • 提供CarContext,这是车载应用的上下文,比普通Context多了很多车载API

来看一个最简单的CarApp实现:

public class MyCarApp extends CarApp {
    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化车载服务连接
        // 我在项目中习惯在这里做一次全局的Car连接检查
    }

    @Override
    public CarActivity createCarActivity(Class<? extends CarActivity> activityClass) {
        // 返回对应的CarActivity实例
        return new MyCarActivity();
    }
}

注意,CarApp必须在AndroidManifest中声明,并且使用android:name=".MyCarApp"。这个坑我踩过——忘了声明,结果应用启动直接崩溃。

11.1.2 CarActivity——车载版的Activity

CarActivity是车载应用中的基本界面单元。它和普通Activity最大的区别在于:

  • 生命周期受CarApp控制,而不是系统直接管理
  • 自带CarContext,可以直接调用车载API
  • 支持多窗口和分屏模式

我曾经在项目中遇到一个问题:普通Activity在车载系统上无法正确响应驾驶模式切换。后来换成CarActivity,一切正常。说白了,这就是“专业的事交给专业的类”。

public class MyCarActivity extends CarActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置布局
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onStart() {
        super.onStart();
        // 车载特有的启动逻辑
    }
}

11.2 Car API使用:CarConnection与CarOccupantZone

车载应用离不开车辆数据。怎么拿到车速、车门状态、空调信息?这就需要用到Car API。这里我重点讲两个最常用的:CarConnectionCarOccupantZone

11.2.1 CarConnection——连接车辆服务的桥梁

CarConnection负责管理应用与车辆服务之间的连接。你想想看,车辆数据是实时变化的,如果连接不稳定,车速显示就会卡顿甚至出错。

使用步骤很简单:

  1. 通过Car.createCar()获取Car实例
  2. 调用car.connect()建立连接
  3. 通过CarConnection监听连接状态
Car car = Car.createCar(context, new Car.CarServiceLifecycleListener() {
    @Override
    public void onConnected(Car car) {
        // 连接成功,可以获取车辆数据了
        Log.d("CarApp", "车辆服务已连接");
    }

    @Override
    public void onDisconnected(Car car) {
        // 连接断开,需要处理重连逻辑
        Log.w("CarApp", "车辆服务断开");
    }
});
car.connect();

嗯,这里要注意:connect()是异步操作,不要在UI线程中等待结果。我见过有同事直接在onCreate里写死循环等连接,结果ANR了。

11.2.2 CarOccupantZone——区分乘客区域

车载系统里,不同座位的人看到的内容可能不同。比如驾驶员不能看视频,但副驾可以。CarOccupantZone就是用来标识“这个界面属于哪个座位”。

它的典型用法:

CarOccupantZoneManager zoneManager = 
    (CarOccupantZoneManager) car.getCarManager(Car.OCCUPANT_ZONE_SERVICE);

// 获取驾驶员区域
CarOccupantZone driverZone = zoneManager.getDriverOccupantZone();
// 获取副驾区域
CarOccupantZone passengerZone = zoneManager.getPassengerOccupantZone();

// 判断当前应用是否在驾驶员区域
if (zoneManager.isDriverOccupantZone(driverZone)) {
    // 限制某些操作,比如禁止播放视频
}

我在做导航应用时,就用这个API实现了“驾驶员模式”——当检测到当前是驾驶员区域时,自动隐藏视频播放按钮。这个功能在车厂验收时是必查项,大家一定要重视。

11.3 车载UI设计规范:安全驾驶与大屏适配

车载UI设计和手机UI设计完全是两码事。手机可以追求炫酷的动画、复杂的交互,但车载UI的第一原则是——安全。

11.3.1 安全驾驶原则

我总结了几条必须遵守的规则:

  • 驾驶时禁止复杂操作:任何需要盯着屏幕超过2秒的操作,都不应该在驾驶时出现
  • 文字大小有底线:最小字号建议不低于16sp,关键信息(如车速)不低于24sp
  • 触控区域要够大:按钮最小尺寸建议48dp×48dp,最好64dp以上
  • 颜色对比要清晰:不要用低对比度的配色,阳光直射下根本看不清
⚠️ 避坑指南
我曾经在一个项目中,把设置按钮做成了32dp大小,结果车厂测试时直接打回——驾驶员在行驶中根本点不准。后来改成了56dp,一次通过。

11.3.2 大屏适配技巧

现在的车载屏幕越来越大,12.3英寸、15.6英寸都很常见。大屏适配有几个要点:

  • 不要简单拉伸:手机布局直接放大,会显得很空旷
  • 善用网格系统:推荐使用8dp或4dp的网格,让元素对齐更规整
  • 信息分层展示:重要信息(车速、导航)放中间,次要信息(音乐、设置)放两侧
  • 支持多窗口:你的应用可能和导航、音乐同时显示,要能自适应
💡 我的习惯
我一般会先设计一个“最小安全区域”——就是驾驶员视线最自然的区域,大概在屏幕中央偏左的位置。所有关键信息都放在这个区域内。次要信息放在两侧,但保证驾驶员不需要转头就能扫到。

11.4 第一个车载应用:车速显示

理论讲完了,我们来动手写一个真正的车载应用——车速显示。这个应用虽然简单,但涵盖了CarApp、CarActivity、Car API的完整使用流程。

11.4.1 项目结构

先看整体结构:

app/
├── src/main/
│   ├── java/com/example/speeddisplay/
│   │   ├── SpeedCarApp.java          // CarApp实现
│   │   ├── SpeedCarActivity.java     // CarActivity实现
│   │   └── SpeedViewModel.java       // 车速数据管理
│   ├── res/
│   │   ├── layout/
│   │   │   └── activity_speed.xml    // 布局文件
│   │   └── values/
│   │       └── strings.xml
│   └── AndroidManifest.xml
└── build.gradle

11.4.2 核心代码实现

第一步:CarApp

public class SpeedCarApp extends CarApp {
    @Override
    public CarActivity createCarActivity(Class<? extends CarActivity> activityClass) {
        return new SpeedCarActivity();
    }
}

第二步:CarActivity + 车速获取

public class SpeedCarActivity extends CarActivity {
    private Car car;
    private VehicleHal vehicleHal;
    private TextView speedText;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_speed);

        speedText = findViewById(R.id.speed_text);

        // 连接车辆服务
        car = Car.createCar(this, new Car.CarServiceLifecycleListener() {
            @Override
            public void onConnected(Car car) {
                // 获取VehicleHal服务
                vehicleHal = (VehicleHal) car.getCarManager(VehicleHal.class);
                // 订阅车速数据
                subscribeSpeed();
            }

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

    private void subscribeSpeed() {
        // 订阅车辆速度属性
        vehicleHal.subscribeProperty(
            VehiclePropertyIds.PERF_VEHICLE_SPEED,
            new VehicleHalCallback() {
                @Override
                public void onPropertyEvent(VehiclePropValue value) {
                    float speed = value.getValue().getFloatValue();
                    // 更新UI(注意要在主线程)
                    runOnUiThread(() -> {
                        speedText.setText(String.format("%.0f km/h", speed));
                    });
                }
            }
        );
    }

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

第三步:布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:background="#1A1A2E">

    <TextView
        android:id="@+id/speed_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-- km/h"
        android:textSize="72sp"
        android:textColor="#00D4AA"
        android:fontFamily="sans-serif-medium" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="当前车速"
        android:textSize="18sp"
        android:textColor="#888888"
        android:layout_marginTop="8dp" />

</LinearLayout>

11.4.3 运行效果

这个应用启动后,会显示一个简洁的车速界面。数字很大,颜色醒目,驾驶员一眼就能看到。嗯,这里要注意:实际部署时,车速数据来源可能不同,有的车厂用PERF_VEHICLE_SPEED,有的用VEHICLE_SPEED_DISPLAY,需要根据具体车型调整。

📌 核心要点回顾
  • CarApp管理应用生命周期,CarActivity管理界面
  • CarConnection负责连接车辆服务,必须异步处理
  • CarOccupantZone用于区分乘客区域,实现安全策略
  • 车载UI以安全为第一原则,大屏适配要信息分层
  • 车速显示应用是车载开发的“Hello World”,但麻雀虽小五脏俱全

11.5 本章知识体系图

下面这张图总结了本章的核心知识结构,大家可以对照着复习:

车载应用开发基础 · 知识体系 车载应用 (CarApp) 应用架构 Car API UI设计规范 CarApp CarActivity CarConnection CarOccupantZone 安全驾驶 大屏适配 第一个车载应用:车速显示 架构 + API + UI → 实践落地

好了,这一章的内容就到这里。从架构到API,从规范到实战,我们一步步把车载应用开发的根基打牢了。下一章我们会深入车辆数据服务,看看怎么获取更多车辆信息,比如转速、油耗、车门状态等。到时候再给大家分享一些我在项目中踩过的坑。

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