Tìm hiểu kiến thức cơ bản về thư viện Ứng dụng dành cho ô tô

1. Trước khi bắt đầu

Trong lớp học lập trình này, bạn tìm hiểu cách xây dựng ứng dụng được tối ưu hoá để chống phân tâm dành cho Android AutoAndroid Automotive OS bằng thư viện ứng dụng Android cho Ô tô (Android for Cars App Library). Trước tiên, bạn sẽ thêm khả năng hỗ trợ Android Auto, sau đó chỉ cần thực hiện thêm một vài việc như tạo một biến thể của ứng dụng có thể chạy trên Android Automotive OS. Sau khi ứng dụng này chạy được trên cả hai nền tảng, bạn sẽ xây dựng thêm một màn hình và một số hoạt động tương tác cơ bản!

Không bao gồm

Bạn cần có

Sản phẩm bạn sẽ tạo ra

Android Auto

Android Automotive OS

Một bản ghi màn hình cho thấy ứng dụng đang chạy trên Android Auto qua Đầu phát trung tâm máy tính (Desktop Head Unit).

Một bản ghi màn hình cho thấy ứng dụng đang chạy trên trình mô phỏng Android Automotive OS.

Kiến thức bạn sẽ học được

  • Cách hoạt động của kiến trúc ứng dụng lưu trữ-ứng dụng khách của Thư viện Ứng dụng cho Ô tô (Car App Library).
  • Cách viết các lớp CarAppService, SessionScreen của riêng bạn.
  • Cách chia sẻ phương thức triển khai của bạn trên cả Android Auto và Android Automotive OS.
  • Cách chạy Android Auto trên máy phát triển của bạn qua Đầu phát trung tâm máy tính.
  • Cách chạy trình mô phỏng Android Automotive OS.

2. Bắt đầu thiết lập

Lấy mã nguồn

  1. Bạn có thể tìm thấy đoạn mã dành cho lớp học lập trình này trong thư mục car-app-library-fundamentals trong kho lưu trữ GitHub car-codelabs. Để sao chép đoạn mã đó, hãy chạy lệnh sau:
git clone https://github.com/android/car-codelabs.git
  1. Ngoài ra, bạn có thể tải kho lưu trữ ở dạng định dạng tệp ZIP:

Mở dự án

  • Sau khi chạy Android Studio, hãy nhập dự án, chỉ chọn thư mục car-app-library-fundamentals/start. Thư mục car-app-library-fundamentals/end chứa đoạn mã giải pháp mà bạn có thể tham khảo bất cứ lúc nào nếu gặp khó khăn hoặc đơn giản là xem toàn bộ dự án.

Làm quen với đoạn mã

  • Sau khi mở dự án trong Android Studio, hãy dành chút thời gian để xem qua đoạn mã khởi đầu.

Xin lưu ý rằng đoạn mã khởi đầu cho ứng dụng này được chia thành hai mô-đun, :app:common:data.

Mô-đun :app phụ thuộc vào mô-đun :common:data.

Mô-đun :app chứa giao diện người dùng và logic của ứng dụng di động, còn mô-đun :common:data chứa lớp dữ liệu mô hình Placekho lưu trữ dùng để đọc mô hình Place. Để đơn giản, kho lưu trữ sẽ đọc dữ liệu từ một danh sách cố định giá trị trong mã, nhưng trong ứng dụng thực thì kho lưu trữ có thể dễ dàng đọc dữ liệu từ cơ sở dữ liệu hoặc máy chủ phụ trợ.

Mô-đun :app bao gồm một phần phụ thuộc trên mô-đun :common:data để mô-đun đó có thể đọc và thể hiện danh sách mô hình Place.

3. Tìm hiểu về Thư viện Ứng dụng Android cho Ô tô

Thư viện Ứng dụng Android cho Ô tô (Android for Cars App Library) là một nhóm thư viện Jetpack cho phép nhà phát triển xây dựng ứng dụng để sử dụng trong xe. Thư viện này mang đến một khung theo mẫu cung cấp giao diện người dùng được tối ưu hoá cho quá trình lái xe, đồng thời đảm nhiệm việc điều chỉnh để thích ứng với các cấu hình phần cứng trong ô tô (ví dụ: phương thức nhập, kích thước màn hình và tỷ lệ khung hình). Cả hai điều này sẽ giúp nhà phát triển dễ dàng xây dựng ứng dụng và tự tin rằng ứng dụng đó sẽ hoạt động tốt trên nhiều loại xe chạy cả Android Auto và Android Automotive OS.

Tìm hiểu cách hoạt động

Các ứng dụng được tạo bằng Thư viện Ứng dụng cho Ô tô sẽ không chạy trực tiếp trên Android Auto hoặc Android Automotive OS. Thay vào đó, các ứng dụng đó dựa vào ứng dụng lưu trữ để giao tiếp với ứng dụng khách và kết xuất giao diện người dùng thay mặt cho ứng dụng khách. Bản thân Android Auto là một ứng dụng lưu trữ và Google Automotive App Host là ứng dụng lưu trữ được sử dụng trên các xe sử dụng Android Automotive OS được cài sẵn Google. Sau đây là các lớp chính của Thư viện Ứng dụng cho Ô tô mà bạn phải mở rộng khi tạo ứng dụng của mình:

CarAppService

CarAppService là một lớp con của lớp Service của Android và đóng vai trò là điểm vào để các ứng dụng lưu trữ giao tiếp với ứng dụng khách (chẳng hạn như ứng dụng bạn sẽ tạo trong lớp học lập trình này). Mục đích chính của lớp đó là tạo các thực thể Session mà ứng dụng lưu trữ sẽ tương tác.

Session

Bạn có thể xem Session là một thực thể của ứng dụng khách chạy từ xa trên màn hình của xe. Giống như các thành phần Android khác, thành phần này có vòng đời riêng có thể dùng để khởi tạo và chia nhỏ các tài nguyên được sử dụng trong suốt quá trình tồn tại của thực thể Session. Có mối quan hệ một với nhiều giữa CarAppServiceSession. Ví dụ: một CarAppService có thể có hai thực thể Session (một dành cho màn hình chính và một dành cho màn hình phân cụm) dành cho các ứng dụng đi theo chỉ dẫn có hỗ trợ màn hình phân cụm.

Screen

Các thực thể Screen chịu trách nhiệm tạo giao diện người dùng được kết xuất bởi ứng dụng lưu trữ. Các giao diện người dùng này được thể hiện bằng các lớp Template, mỗi lớp mô hình hoá một kiểu bố cục cụ thể, chẳng hạn như lưới (grid) hoặc danh sách (list). Mỗi Session quản lý một ngăn xếp gồm nhiều thực thể Screen xử lý luồng người dùng thông qua nhiều phần của ứng dụng. Giống như Session, Screen cũng có vòng đời riêng mà bạn có thể kết nối vào đó.

Sơ đồ về cách hoạt động của Thư viện Ứng dụng cho Ô tô. Ở phía bên trái là hai hộp mang tiêu đề "Display" (Hiển thị). Ở giữa có một ô mang tiêu đề "Host" (Lưu trữ). Ở bên phải có một hộp mang tiêu đề "CarAppService". Trong hộp CarAppService, có hai hộp, cả hai đều mang tiêu đề "Session" (Phiên). Trong Phiên đầu tiên, có ba hộp "Screen" (Màn hình) chồng lên nhau. Trong Phiên thứ hai, có hai hộp "Screen" (Màn hình) chồng lên nhau. Có các mũi tên giữa mỗi Display và Host, cũng như giữa các Host và Session để cho biết cách ứng dụng lưu trữ giao tiếp với từng thành phần.

Bạn sẽ viết một CarAppService, SessionScreen trong phần Viết CarAppService của lớp học lập trình này, vì vậy đừng lo lắng nếu mọi thứ vẫn chưa ổn.

4. Thiết lập cấu hình ban đầu

Để bắt đầu, hãy thiết lập mô-đun chứa CarAppService và khai báo các phần phụ thuộc của mô-đun đó.

Tạo mô-đun car-app-service

  1. Chọn mô-đun :common trong cửa sổ Project (Dự án), nhấp chuột phải và chọn New > Module (Mới > Mô-đun).
  2. Trình hướng dẫn của mô-đun này sẽ mở ra, trong đó hãy chọn mẫu Android Library (Thư viện Android) (để mô-đun này có thể được sử dụng làm phần phụ thuộc cho các mô-đun khác) trong danh sách ở phía bên trái, rồi sử dụng các giá trị sau:
  • Tên mô-đun: :common:car-app-service
  • Tên gói: com.example.places.carappservice
  • SDK tối thiểu: API 23: Android 6.0 (Marshmallow)

Trình hướng dẫn Create New Module (Tạo mô-đun mới) với các giá trị được thiết lập theo mô tả ở bước này.

Thiết lập phần phụ thuộc

  1. Trong tệp build.gradle ở cấp dự án, hãy thêm phần khai báo biến cho các phiên bản Thư viện Ứng dụng cho Ô tô như sau. Việc này giúp bạn dễ dàng sử dụng cùng một phiên bản trên từng mô-đun trong ứng dụng.

build.gradle (Dự án: Places)

buildscript {
    ext {
        // All versions can be found at https://developer.android.com/jetpack/androidx/releases/car-app
        car_app_library_version = '1.3.0-rc01'
        ...
    }
}
  1. Tiếp theo, hãy thêm hai phần phụ thuộc vào tệp build.gradle của mô-đun :common:car-app-service.
  • androidx.car.app:app. Đây là cấu phần phần mềm chính của Thư viện Ứng dụng cho Ô tô và bao gồm tất cả các lớp cốt lõi được sử dụng khi tạo ứng dụng. Có ba cấu phần phần mềm khác tạo nên thư viện này, androidx.car.app:app-projected tương ứng với chức năng dành riêng cho Android Auto, androidx.car.app:app-automotive tương ứng với mã chức năng của Android Automotive OS, và androidx.car.app:app-testing tương ứng với một số trình trợ giúp hữu ích cho việc kiểm thử đơn vị. Sau này, bạn sẽ sử dụng app-projectedapp-automotive trong lớp học lập trình này.
  • :common:data. Đây chính là mô-đun dữ liệu được ứng dụng di động hiện tại sử dụng và cho phép sử dụng cùng một nguồn dữ liệu cho trải nghiệm trong mọi phiên bản ứng dụng.

build.gradle (Mô-đun :common:car-app-service)

dependencies {
    ...
    implementation "androidx.car.app:app:$car_app_library_version"
    implementation project(":common:data")
    ...
}

Với thay đổi này, biểu đồ phần phụ thuộc cho các mô-đun riêng của ứng dụng sẽ như sau:

Các mô-đun :app và :common:car-app-service đều phụ thuộc vào mô-đun :common:data.

Hiện tại, các phần phụ thuộc đã được thiết lập, đã đến lúc viết CarAppService!

5. Viết CarAppService

  1. Hãy bắt đầu bằng cách tạo một tệp có tên PlacesCarAppService.kt trong gói carappservice trong mô-đun :common:car-app-service.
  2. Trong tệp này, hãy tạo một lớp có tên PlacesCarAppService, lớp này sẽ mở rộng CarAppService.

PlacesCarAppService.kt

class PlacesCarAppService : CarAppService() {

    override fun createHostValidator(): HostValidator {
        return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
    }

    override fun onCreateSession(): Session {
        // PlacesSession will be an unresolved reference until the next step
        return PlacesSession()
    }
}

Lớp trừu tượng CarAppService sẽ triển khai các phương thức Service (ví dụ: onBindonUnbind) cho bạn cũng như ngăn chặn việc ghi đè thêm các phương thức đó để đảm bảo khả năng tương tác thích hợp với các ứng dụng lưu trữ. Bạn chỉ phải triển khai createHostValidatoronCreateSession.

HostValidator mà bạn trả về từ createHostValidator sẽ được tham chiếu khi CarAppService đang được liên kết để đảm bảo rằng ứng dụng lưu trữ là đáng tin cậy và mối liên kết đó sẽ gặp lỗi nếu ứng dụng lưu trữ không phù hợp với các tham số mà bạn xác định. Đối với mục đích của lớp học lập trình này (và hoạt động kiểm thử nói chung), ALLOW_ALL_HOSTS_VALIDATOR sẽ giúp bạn dễ dàng đảm bảo về mặt kết nối cho ứng dụng của mình (nhưng không nên dùng khi phát hành công khai). Hãy xem tài liệu về createHostValidator để biết thêm về cách định cấu hình đối tượng này cho ứng dụng phát hành công khai.

Đối với một ứng dụng đơn giản như ứng dụng này, onCreateSession có thể đơn giản là trả về một thực thể Session. Trong một ứng dụng phức tạp hơn, đây sẽ là nơi phù hợp để khởi tạo các tài nguyên tồn tại lâu dài như các chỉ số. Đồng thời, các ứng dụng khách dùng để ghi nhật ký sẽ được sử dụng trong khi ứng dụng của bạn đang chạy trên xe.

  1. Sau cùng, bạn cần thêm phần tử <service> tương ứng với PlacesCarAppService trong tệp AndroidManifest.xml của mô-đun :common:car-app-service để cho phép hệ điều hành (và các ứng dụng khác như ứng dụng lưu trữ) biết nó tồn tại.

AndroidManifest.xml (:common:car-app-service)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!--
        This AndroidManifest.xml will contain all of the elements that should be shared across the
        Android Auto and Automotive OS versions of the app, such as the CarAppService <service> element
    -->

    <application>
        <service
            android:name="com.example.places.carappservice.PlacesCarAppService"
            android:exported="true">
            <intent-filter>
                <action android:name="androidx.car.app.CarAppService" />
                <category android:name="androidx.car.app.category.POI" />
            </intent-filter>
        </service>
    </application>
</manifest>

Có hai yếu tố quan trọng cần lưu ý ở đây:

  • Phần tử <action> cho phép ứng dụng lưu trữ (và trình chạy) tìm được ứng dụng đó.
  • Phần tử <category> khai báo về danh mục của ứng dụng, xác định tiêu chí chất lượng mà ứng dụng đó sẽ phải đáp ứng (bạn sẽ tìm hiểu kỹ hơn về điều này sau). Có thể sử dụng các giá trị androidx.car.app.category.NAVIGATIONandroidx.car.app.category.IOT.

Tạo lớp PlacesSession

  • Tạo tệp một PlacesCarAppService.kt rồi thêm đoạn mã sau:

PlacesCarAppService.kt

class PlacesSession : Session() {
    override fun onCreateScreen(intent: Intent): Screen {
        // MainScreen will be an unresolved reference until the next step
        return MainScreen(carContext)
    }
}

Đối với một ứng dụng đơn giản như ứng dụng này, bạn có thể quay lại màn hình chính ngay trong onCreateScreen. Tuy nhiên, vì phương thức này lấy Intent làm tham số, nên một ứng dụng nhiều tính năng hơn cũng có thể đọc từ đó và điền sẵn vào ngăn xếp lui của màn hình hoặc sử dụng một số logic điều kiện khác.

Tạo lớp MainScreen

Tiếp theo, hãy tạo một gói mới tên là screen.

  1. Nhấp phải vào gói com.example.places.carappservice rồi chọn New (Mới) > Package (Gói) (tên đầy đủ của gói này sẽ là com.example.places.carappservice.screen). Đây là nơi bạn sẽ đưa vào đó tất cả lớp con Screen cho ứng dụng.
  2. Trong gói screen, hãy tạo một tệp tên là MainScreen.kt để chứa lớp MainScreen (mở rộng Screen). Hiện tại, ứng dụng sẽ cho thấy một thông điệp Hello, world! đơn giản bằng cách sử dụng PaneTemplate.

MainScreen.kt

class MainScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder()
            .setTitle("Hello, world!")
            .build()
        
        val pane = Pane.Builder()
            .addRow(row)
            .build()

        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

6. Thêm khả năng hỗ trợ Android Auto

Mặc dù hiện tại bạn đã triển khai tất cả logic cần thiết để thiết lập và chạy ứng dụng, nhưng bạn vẫn sẽ cần thiết lập 2 phần cấu hình nữa trước khi có thể chạy ứng dụng đó trên Android Auto.

Thêm phần phụ thuộc vào mô-đun car-app-service

Trong tệp build.gradle của mô-đun :app, hãy thêm đoạn mã sau:

build.gradle (Mô-đun :app)

dependencies {
    ...
    implementation project(path: ':common:car-app-service')
    ...
}

Với thay đổi này, biểu đồ phần phụ thuộc cho các mô-đun riêng của ứng dụng sẽ như sau:

Các mô-đun :app và :common:car-app-service đều phụ thuộc vào mô-đun :common:data. Mô-đun :app cũng phụ thuộc vào mô-đun :common:car-app-service.

Thay đổi này sẽ gói đoạn mã bạn vừa viết trong mô-đun :common:car-app-service cùng với các thành phần khác có trong Thư viện Ứng dụng cho Ô tô (ví dụ: hoạt động cấp quyền được cung cấp).

Khai báo siêu dữ liệu com.google.android.gms.car.application

  1. Nhấp chuột phải vào mô-đun :common:car-app-service, chọn New > Android Resource File (Mới > Tệp tài nguyên Android), rồi ghi đè các giá trị sau:
  • Tên tệp: automotive_app_desc.xml
  • Loại tài nguyên: XML
  • Phần tử gốc: automotiveApp

Trình hướng dẫn New Resource File (Tệp tài nguyên mới) với các giá trị được thiết lập theo mô tả trong bước này.

  1. Trong tệp đó, hãy thêm phần tử <uses> sau để khai báo rằng ứng dụng của bạn sẽ sử dụng các mẫu do Thư viện Ứng dụng cho Ô tô cung cấp.

automotive_app_desc.xml

<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
    <uses name="template"/>
</automotiveApp>
  1. Trong tệp AndroidManifest.xml của mô-đun :app, hãy thêm phần tử <meta-data> sau (tham chiếu đến tệp automotive_app_desc.xml mà bạn vừa tạo).

AndroidManifest.xml (:app)

<application ...>

    <meta-data
        android:name="com.google.android.gms.car.application"
        android:resource="@xml/automotive_app_desc" />

    ...

</application>

Android Auto sẽ đọc tệp này và được cho biết về những chức năng mà ứng dụng của bạn hỗ trợ (trong trường hợp này là ứng dụng sử dụng hệ thống tạo mẫu của Thư viện Ứng dụng cho Ô tô). Thông tin này sau đó được sẽ dùng để xử lý các hành vi như thêm ứng dụng vào trình chạy Android Auto và mở ứng dụng từ thông báo.

Không bắt buộc: Nghe những thay đổi về phép chiếu

Đôi khi, bạn muốn biết liệu thiết bị của người dùng có được kết nối với ô tô hay không. Bạn có thể thực hiện việc này bằng cách sử dụng CarConnection API. API này cung cấp LiveData cho phép bạn quan sát trạng thái kết nối.

  1. Để dùng CarConnection API, trước tiên hãy thêm phần phụ thuộc vào mô-đun :app trên cấu phần phần mềm androidx.car.app:app.

build.gradle (Mô-đun :app)

dependencies {
    ...
    implementation "androidx.car.app:app:$car_app_library_version"
    ...
}
  1. Để minh hoạ, bạn có thể tạo một Thành phần kết hợp (Composable) đơn giản như sau để cho thấy trạng thái kết nối hiện tại. Trong ứng dụng thực, trạng thái này có thể được ghi lại trong một số nhật ký, dùng để tắt một số chức năng trên màn hình điện thoại trong khi chiếu, hoặc cho mục đích khác.

MainActivity.kt

@Composable
fun ProjectionState(carConnectionType: Int, modifier: Modifier = Modifier) {
    val text = when (carConnectionType) {
        CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not projecting"
        CarConnection.CONNECTION_TYPE_NATIVE -> "Running on Android Automotive OS"
        CarConnection.CONNECTION_TYPE_PROJECTION -> "Projecting"
        else -> "Unknown connection type"
    }

    Text(
        text = text,
        style = MaterialTheme.typography.bodyMedium,
        modifier = modifier
    )
}
  1. Hiện bạn đã có cách hiển thị dữ liệu, hãy đọc và truyền dữ liệu đó vào Thành phần kết hợp, như minh hoạ trong đoạn mã sau.

MainActivity.kt

setContent {
    val carConnectionType by CarConnection(this).type.observeAsState(initial = -1)
    PlacesTheme {
        // A surface container using the 'background' color from the theme
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            Column {
                Text(
                    text = "Places",
                    style = MaterialTheme.typography.displayLarge,
                    modifier = Modifier.padding(8.dp)
                )
                ProjectionState(
                    carConnectionType = carConnectionType,
                    modifier = Modifier.padding(8.dp)
                )
                PlaceList(places = PlacesRepository().getPlaces())
            }
        }
    }
}
  1. Nếu bạn chạy ứng dụng, ứng dụng sẽ thông báo Hiện không chiếu.

Hiện tại có thêm một dòng văn bản trên màn hình cho trạng thái chiếu, với nội dung &quot;Not projecting&quot; (&quot;Hiện không chiếu&quot;)

7. Kiểm thử bằng Đầu phát trung tâm máy tính (DHU)

Sau khi triển khai CarAppService và chuẩn bị xong cấu hình Android Auto, đã đến lúc chạy ứng dụng và xem kết quả.

  1. Cài đặt ứng dụng trên điện thoại của bạn, rồi làm theo hướng dẫn cài đặt và chạy DHU.

Khi DHU được thiết lập và chạy, bạn sẽ thấy biểu tượng ứng dụng trong trình chạy (nếu không, hãy kiểm tra kỹ xem bạn đã làm theo tất cả bước trong phần trước chưa, sau đó thoát và khởi động lại DHU từ thiết bị đầu cuối).

  1. Mở ứng dụng qua trình chạy

Trình chạy Android Auto cho thấy lưới ứng dụng, trong đó có cả ứng dụng Places (Địa điểm).

Ôi, gặp sự cố rồi!

Xuất hiện màn hình lỗi với thông báo &quot;Android Auto has encountered an unexpected error&quot; (&quot;Android Auto đã gặp lỗi không mong muốn&quot;). Có một nút chuyển gỡ lỗi ở góc trên bên phải màn hình.

  1. Để biết lý do ứng dụng gặp sự cố, bạn có thể chuyển đổi biểu tượng gỡ lỗi ở góc trên cùng bên phải (chỉ xuất hiện khi chạy trên DHU) hoặc kiểm tra Logcat trong Android Studio.

Màn hình lỗi tương tự như hình trước, nhưng nút chuyển gỡ lỗi đang bật. Dấu vết ngăn xếp đang hiện trên màn hình.

Error: [type: null, cause: null, debug msg: java.lang.IllegalArgumentException: Min API level not declared in manifest (androidx.car.app.minCarApiLevel)
        at androidx.car.app.AppInfo.retrieveMinCarAppApiLevel(AppInfo.java:143)
        at androidx.car.app.AppInfo.create(AppInfo.java:91)
        at androidx.car.app.CarAppService.getAppInfo(CarAppService.java:380)
        at androidx.car.app.CarAppBinder.getAppInfo(CarAppBinder.java:255)
        at androidx.car.app.ICarApp$Stub.onTransact(ICarApp.java:182)
        at android.os.Binder.execTransactInternal(Binder.java:1285)
        at android.os.Binder.execTransact(Binder.java:1244)
]

Trên nhật ký này, bạn có thể thấy rằng có một phần khai báo bị thiếu trong tệp kê khai về cấp độ API tối thiểu mà ứng dụng hỗ trợ. Trước khi thêm mục đó, tốt nhất bạn nên hiểu lý do khiến nó cần thiết.

Cũng như Android, Thư viện Ứng dụng cho Ô tô cũng có khái niệm về các cấp độ API, vì cần phải có quy định ràng buộc giữa ứng dụng lưu trữ và ứng dụng khách để chúng giao tiếp với nhau. Các ứng dụng lưu trữ hỗ trợ một cấp API nhất định cũng như các tính năng liên quan đến cấp API đó (và cả các tính năng từ các cấp trước đó để tương thích ngược). Ví dụ: SignInTemplate có thể được sử dụng trên các ứng dụng lưu trữ ở API cấp 2 trở lên. Tuy nhiên, nếu bạn cố sử dụng nó trên ứng dụng lưu trữ chỉ hỗ trợ API cấp 1, thì ứng dụng lưu trữ đó sẽ không biết về loại mẫu và sẽ không thể thực hiện bất cứ tác vụ có ý nghĩa nào với loại mẫu đó.

Trong quá trình liên kết ứng dụng lưu trữ với ứng dụng khách, các cấp độ API được hỗ trợ phải có một mức độ trùng nhau nào đó để có thể liên kết thành công. Ví dụ: nếu ứng dụng lưu trữ chỉ hỗ trợ API cấp 1 nhưng ứng dụng khách không thể chạy nếu không có các tính năng của API cấp 2 (như trong phần khai báo của tệp kê khai này cho thấy), thì các ứng dụng này sẽ không kết nối được với nhau vì ứng dụng khách sẽ không thể chạy thành công trên ứng dụng lưu trữ. Do đó, yêu cầu về cấp độ API tối thiểu phải được ứng dụng khách khai báo trong tệp kê khai để đảm bảo rằng chỉ ứng dụng lưu trữ hỗ trợ được cấp độ API đó mới được liên kết.

  1. Để thiết lập cấp độ API tối thiểu được hỗ trợ, hãy thêm phần tử <meta-data> sau vào tệp AndroidManfiest.xml của mô-đun :common:car-app-service:

AndroidManifest.xml (:common:car-app-service)

<application>
    <meta-data
        android:name="androidx.car.app.minCarApiLevel"
        android:value="1" />
    <service android:name="com.example.places.carappservice.PlacesCarAppService" ...>
        ...
    </service>
</application>
  1. Cài đặt lại ứng dụng rồi khởi chạy ứng dụng đó trên DHU, bạn sẽ thấy như sau:

Ứng dụng hiện màn hình &quot;Hello, world&quot; cơ bản

Để hoàn thiện, bạn cũng có thể thử đặt minCarApiLevel thành một giá trị lớn (ví dụ: 100) để xem điều gì xảy ra khi cố khởi động ứng dụng nếu ứng dụng lưu trữ và ứng dụng khách không tương thích (gợi ý: sẽ gặp sự cố, tương tự như khi không có giá trị nào được thiết lập).

Điều quan trọng cần lưu ý là: cũng như Android, bạn có thể sử dụng các tính năng của API cao hơn API tối thiểu được khai báo nếu bạn xác minh được trong thời gian chạy rằng ứng dụng lưu trữ hỗ trợ cấp độ API cần thiết.

Không bắt buộc: Nghe những thay đổi về phép chiếu

  • Nếu đã thêm trình nghe CarConnection ở bước trước, bạn sẽ thấy trạng thái đã cập nhật trên điện thoại của mình khi DHU đang chạy, như minh hoạ dưới đây:

Dòng văn bản cho thấy trạng thái chiếu hiện có nội dung &quot;Projecting&quot; (&quot;Đang chiếu&quot;) vì điện thoại đã kết nối với DHU.

8. Thêm khả năng hỗ trợ Android Automotive OS

Nay bạn đã thiết lập và chạy Android Auto, đã đến lúc mở rộng hơn nữa để hỗ trợ cả Android Automotive OS.

Tạo mô-đun :automotive

  1. Để tạo một mô-đun chứa đoạn mã dành riêng cho phiên bản ứng dụng Android Automotive OS, hãy mở File > New > New Module… (Tệp > Mới > Mô-đun mới…) trong Android Studio, chọn Automotive trong danh sách loại mẫu ở bên trái, sau đó sử dụng các giá trị sau:
  • Tên ứng dụng/thư viện: Places (giống như ứng dụng chính, nhưng bạn cũng có thể chọn tên khác nếu muốn)
  • Tên mô-đun: automotive
  • Tên gói: com.example.places.automotive
  • Ngôn ngữ: Kotlin
  • SDK tối thiểu: API 29: Android 10.0 (Q) — như đề cập trước đó khi tạo mô-đun :common:car-app-service, tất cả xe Android Automotive OS có hỗ trợ ứng dụng tạo bằng Thư viện Ứng dụng cho Ô tô đều chạy ở cấp độ API tối thiểu là 29.

Trình hướng dẫn Create New Module (Tạo mô-đun mới) cho mô-đun Android Automotive OS cho thấy các giá trị được liệt kê trong bước này.

  1. Nhấp vào Next (Tiếp theo), rồi chọn No Activity (Không có hoạt động) trên màn hình tiếp theo, sau cùng nhấp vào Finish (Hoàn tất).

Trang thứ hai của trình hướng dẫn Create New Module (Tạo mô-đun mới). Ba lựa chọn xuất hiện: &quot;No Activity&quot; (&quot;Không có hoạt động&quot;), &quot;Media Service&quot; (&quot;Dịch vụ đa phương tiện&quot;) và &quot;Messaging Service&quot; (&quot;Dịch vụ nhắn tin&quot;). Lựa chọn &quot;No Activity&quot; (&quot;Không có hoạt động&quot;) được chọn.

Thêm phần phụ thuộc

Cũng như Android Auto, bạn cần khai báo phần phụ thuộc trên mô-đun :common:car-app-service. Nhờ vậy, bạn có thể chia sẻ phương thức triển khai của mình trên cả hai nền tảng!

Ngoài ra, bạn cần thêm phần phụ thuộc vào cấu phần phần mềm androidx.car.app:app-automotive. Không giống như cấu phần phần mềm androidx.car.app:app-projected (không bắt buộc đối với Android Auto), phần phụ thuộc này là bắt buộc trên Android Automotive OS vì chứa CarAppActivity dùng để chạy ứng dụng của bạn.

  1. Để thêm phần phụ thuộc, hãy mở tệp build.gradle, rồi chèn đoạn mã sau:

build.gradle (Mô-đun :automotive)

dependencies {
    ...
    implementation project(':common:car-app-service')
    implementation "androidx.car.app:app-automotive:$car_app_library_version"
    ...
}

Với thay đổi này, biểu đồ phần phụ thuộc cho các mô-đun riêng của ứng dụng sẽ như sau:

Các mô-đun :app và :common:car-app-service đều phụ thuộc vào mô-đun :common:data. Các mô-đun :app và :automotive phụ thuộc vào mô-đun :common:car-app-service.

Thiết lập tệp kê khai

  1. Trước tiên, bạn cần khai báo hai tính năng (android.hardware.type.automotiveandroid.software.car.templates_host) bắt buộc.

android.hardware.type.automotive là một tính năng hệ thống cho biết thiết bị là một chiếc xe (xem FEATURE_AUTOMOTIVE để biết thêm chi tiết). Chỉ những ứng dụng đánh dấu tính năng này là bắt buộc mới có thể được gửi tới kênh Automotive OS trên Play Console (và những ứng dụng được gửi đến các kênh khác sẽ không thể đòi hỏi tính năng này). android.software.car.templates_host là một tính năng hệ thống chỉ có trên những xe có ứng dụng lưu trữ mẫu bắt buộc để chạy các ứng dụng mẫu.

AndroidManifest.xml (:automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-feature
        android:name="android.hardware.type.automotive"
        android:required="true" />
    <uses-feature
        android:name="android.software.car.templates_host"
        android:required="true" />
    ...
</manifest>
  1. Tiếp theo, bạn cần khai báo một số tính năng là không bắt buộc.

Điều này nhằm đảm bảo rằng ứng dụng của bạn tương thích với nhiều loại phần cứng có trên ô tô được cài sẵn Google. Ví dụ: nếu ứng dụng của bạn cần đến tính năng android.hardware.screen.portrait thì ứng dụng đó sẽ không tương thích với những xe có màn hình ngang vì hầu hết màn hình xe đều có hướng cố định. Đây là lý do tại sao thuộc tính android:required được thiết lập thành false cho các tính năng này.

AndroidManifest.xml (:automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <uses-feature
        android:name="android.hardware.wifi"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.portrait"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.landscape"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />
    ...
</manifest>
  1. Tiếp theo, bạn cần thêm tham chiếu đến tệp automotive_app_desc.xml như đã thực hiện với Android Auto.

Hãy lưu ý rằng lần này thuộc tính android:name khác với trước đây (là com.android.automotive thay vì com.google.android.gms.car.application). Theo như trước đây, thuộc tính này tham chiếu đến tệp automotive_app_desc.xml trong mô-đun :common:car-app-service, tức là cùng một tài nguyên được sử dụng trên cả Android Auto và Android Automotive OS. Hãy lưu ý rằng phần tử <meta-data> nằm trong phần tử <application> (vì vậy bạn phải thay đổi thẻ application để không tự đóng)!

AndroidManifest.xml (:automotive)

<application>
    ...
    <meta-data android:name="com.android.automotive"
        android:resource="@xml/automotive_app_desc"/>
    ...
</application>
  1. Sau cùng, bạn cần thêm phần tử <activity> cho CarAppActivity có trong thư viện.

AndroidManifest.xml (:automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <application ...>
        ...
        <activity
            android:name="androidx.car.app.activity.CarAppActivity"
            android:exported="true"
            android:launchMode="singleTask"
            android:theme="@android:style/Theme.DeviceDefault.NoActionBar">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="distractionOptimized"
                android:value="true" />
        </activity>
    </application>
</manifest>

Và sau đây là tác dụng của tất cả những điều đó:

  • android:name liệt kê tên lớp đủ điều kiện của lớp CarAppActivity từ gói app-automotive.
  • android:exported được thiết lập thành true do đó Activity phải có thể được khởi chạy bởi một ứng dụng thay vì tự khởi chạy (trình chạy).
  • android:launchMode được thiết lập thành singleTask, do đó chỉ có duy nhất một thực thể CarAppActivity tại một thời điểm.
  • android:theme được thiết lập thành @android:style/Theme.DeviceDefault.NoActionBar để ứng dụng chiếm toàn bộ không gian màn hình có thể sử dụng.
  • Bộ lọc ý định cho biết đây là trình chạy Activity cho ứng dụng.
  • Có một phần tử <meta-data> cho hệ thống biết rằng ứng dụng có thể được sử dụng khi áp dụng các quy tắc hạn chế về trải nghiệm người dùng, chẳng hạn như khi xe đang di chuyển.

Không bắt buộc: sao chép các biểu tượng trình khởi chạy từ mô-đun :app

Vì bạn vừa tạo mô-đun :automotive nên mô-đun đó có biểu trưng Android màu xanh lục mặc định.

  • Nếu muốn, hãy sao chép và dán thư mục tài nguyên mipmap từ mô-đun :app vào mô-đun :automotive để sử dụng các biểu tượng trình khởi chạy giống như ứng dụng di động!

9. Kiểm thử bằng trình mô phỏng Android Automotive OS

Cài đặt Automotive bằng hình ảnh Hệ thống Cửa hàng Play

  1. Trước tiên, hãy mở Trình quản lý SDK trong Android Studio rồi chọn thẻ SDK Platforms (Nền tảng SDK) nếu chưa chọn. Ở góc dưới bên phải của cửa sổ Trình quản lý SDK, hãy đảm bảo rằng hộp Show package details (Hiện thông tin về gói) được chọn.
  2. Cài đặt một hoặc nhiều hình ảnh hệ thống sau đây dành cho trình mô phỏng. Các hình ảnh hệ thống chỉ chạy được trên máy có cùng kiến trúc (x86/ARM) với chính chúng.
  • Android 12L > Hình ảnh hệ thống Automotive có Cửa hàng Play dành cho Intel x86 Atom_64
  • Android 12L > Hình ảnh hệ thống Automotive có Cửa hàng Play dành cho ARM 64 v8a
  • Android 11 > Hình ảnh hệ thống Automotive có Cửa hàng Play dành cho Intel x86 Atom_64
  • Android 10 > Hình ảnh hệ thống Automotive có Cửa hàng Play dành cho Intel x86 Atom_64

Tạo thiết bị Android ảo Android Automotive OS

  1. Sau khi mở Device Manager (Trình quản lý thiết bị), hãy chọn Automotive trong cột Category (Danh mục) ở bên trái cửa sổ. Sau đó, chọn định nghĩa thiết bị Automotive (1024p landscape) (Automotive (hướng ngang 1024p)) trong danh sách rồi nhấp vào Next (Tiếp theo).

Trình hướng dẫn Virtual Device Configuration (Cấu hình thiết bị ảo) cho thấy cấu hình phần cứng &quot;Automotive (1024p landscape)&quot; (&quot;Automotive (hướng ngang 1024p)&quot;) được chọn.

  1. Trên trang tiếp theo, hãy chọn hình ảnh hệ thống từ bước trước (nếu bạn chọn hình ảnh Android 11/API 30, thì có thể hình ảnh đó nằm trong thẻ x86 Images (Hình ảnh x86) chứ không phải thẻ Recommended (Đề xuất) mặc định). Nhấp vào Next (Tiếp theo) rồi chọn mọi lựa chọn nâng cao mà bạn muốn trước khi tạo AVD bằng cách nhấp vào Finish (Hoàn tất).

Chạy ứng dụng

  1. Chạy ứng dụng trên trình mô phỏng bạn vừa tạo bằng cấu hình chạy automotive.

Các

Trong lần đầu chạy ứng dụng, có thể bạn sẽ thấy một màn hình như sau:

Ứng dụng hiện màn hình có nội dung &quot;System update required&quot; (&quot;Yêu cầu cập nhật hệ thống&quot;) với nút có nội dung &quot;Check for updates&quot; (&quot;Kiểm tra bản cập nhật&quot;) ở bên dưới.

Nếu trường hợp đó xảy ra, hãy nhấp vào nút Check for updates (Kiểm tra bản cập nhật). Nút này sẽ đưa bạn đến trang dành cho ứng dụng Google Automotive App Host trên Cửa hàng Play. Tại đó, bạn nên nhấp vào nút Cài đặt. Nếu chưa đăng nhập khi nhấp vào nút Check for updates (Kiểm tra bản cập nhật), bạn sẽ được chuyển sang quy trình đăng nhập. Sau khi đăng nhập, bạn có thể mở lại ứng dụng để nhấp vào nút và quay lại trang Cửa hàng Play.

Trang dành cho ứng dụng Google Automotive App Host trên Cửa hàng Play – có nút &quot;Cài đặt&quot; ở góc trên bên phải.

  1. Sau cùng, khi ứng dụng lưu trữ đã được cài đặt, hãy mở lại ứng dụng trên trình chạy (biểu tượng lưới 9 chấm ở hàng dưới cùng) và bạn sẽ thấy như sau:

Ứng dụng hiện màn hình &quot;Hello, world&quot; cơ bản

Trong bước tiếp theo, bạn sẽ thực hiện các thay đổi trong mô-đun :common:car-app-service để hiện danh sách địa điểm và cho phép người dùng bắt đầu đi theo chỉ dẫn đến vị trí đã chọn trong một ứng dụng khác.

10. Thêm bản đồ và màn hình chi tiết

Thêm bản đồ vào màn hình chính

  1. Để bắt đầu, hãy thay thế đoạn mã trong phương thức onGetTemplate của lớp MainScreen bằng đoạn mã sau:

MainScreen.kt

override fun onGetTemplate(): Template {
    val placesRepository = PlacesRepository()
    val itemListBuilder = ItemList.Builder()
        .setNoItemsMessage("No places to show")

    placesRepository.getPlaces()
        .forEach {
            itemListBuilder.addItem(
                Row.Builder()
                    .setTitle(it.name)
                    // Each item in the list *must* have a DistanceSpan applied to either the title
                    // or one of the its lines of text (to help drivers make decisions)
                    .addText(SpannableString(" ").apply {
                        setSpan(
                            DistanceSpan.create(
                                Distance.create(Math.random() * 100, Distance.UNIT_KILOMETERS)
                            ), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
                        )
                    })
                    .setOnClickListener { TODO() }
                    // Setting Metadata is optional, but is required to automatically show the
                    // item's location on the provided map
                    .setMetadata(
                        Metadata.Builder()
                            .setPlace(Place.Builder(CarLocation.create(it.latitude, it.longitude))
                                // Using the default PlaceMarker indicates that the host should
                                // decide how to style the pins it shows on the map/in the list
                                .setMarker(PlaceMarker.Builder().build())
                                .build())
                            .build()
                    ).build()
            )
        }

    return PlaceListMapTemplate.Builder()
        .setTitle("Places")
        .setItemList(itemListBuilder.build())
        .build()
}

Đoạn mã này sẽ đọc danh sách thực thể Place từ PlacesRepository và sau đó chuyển đổi từng thực thể đó thành một Row để thêm vào ItemList do PlaceListMapTemplate hiển thị.

  1. Chạy lại ứng dụng (trên một hoặc cả hai nền tảng) để xem kết quả!

Android Auto

Android Automotive OS

Một dấu vết ngăn xếp khác sẽ xuất hiện do có lỗi

Ứng dụng vừa gặp sự cố và người dùng được đưa trở lại trình chạy sau khi mở ứng dụng đó.

Ồ, lại một lỗi nữa – có vẻ như là thiếu quyền.

java.lang.SecurityException: The car app does not have a required permission: androidx.car.app.MAP_TEMPLATES
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2373)
        at android.os.Parcel.createException(Parcel.java:2357)
        at android.os.Parcel.readException(Parcel.java:2340)
        at android.os.Parcel.readException(Parcel.java:2282)
        ...
  1. Để sửa lỗi, hãy thêm phần tử <uses-permission> sau đây vào tệp kê khai của mô-đun :common:car-app-service.

Mọi ứng dụng sử dụng PlaceListMapTemplate đều phải khai báo quyền này, nếu không ứng dụng sẽ gặp sự cố như vừa minh hoạ. Xin lưu ý rằng chỉ những ứng dụng khai báo danh mụcandroidx.car.app.category.POI mới có thể sử dụng mẫu này, tương ứng với quyền này.

AndroidManifest.xml (:common:car-app-service)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="androidx.car.app.MAP_TEMPLATES" />
    ...
</manifest>

Nếu bạn chạy ứng dụng sau khi thêm quyền, kết quả sẽ giống như sau trên từng nền tảng:

Android Auto

Android Automotive OS

Một danh sách vị trí xuất hiện phía bên trái màn hình và một bản đồ có các ghim tương ứng với những vị trí xuất hiện phía sau, lấp đầy phần còn lại của màn hình.

Một danh sách vị trí xuất hiện phía bên trái màn hình và một bản đồ có các ghim tương ứng với những vị trí xuất hiện phía sau, lấp đầy phần còn lại của màn hình.

Việc kết xuất bản đồ được ứng dụng lưu trữ xử lý cho bạn khi bạn cung cấp Metadata cần thiết!

Thêm màn hình chi tiết

Tiếp theo, đã đến lúc thêm màn hình chi tiết để cho phép người dùng xem thêm thông tin về một vị trí cụ thể và chọn đi theo chỉ dẫn đến vị trí đó bằng ứng dụng đi theo chỉ dẫn mà họ ưu tiên dùng, hoặc quay lại danh sách địa điểm khác. Bạn có thể thực hiện điều này có thể được bằng cách sử dụng PaneTemplate (cho phép bạn hiển thị tối đa 4 hàng thông tin bên cạnh các nút hành động không bắt buộc).

  1. Trước tiên, hãy nhấp chuột phải vào thư mục res trong mô-đun :common:car-app-service, nhấp vào New > Vector Asset (Mới > Thành phần vectơ), rồi tạo biểu tượng đi theo chỉ dẫn bằng cách sử dụng cấu hình sau:
  • Loại thành phần: Clip art
  • Hình mẫu: navigation
  • Tên: baseline_navigation_24
  • Kích thước: 24dp x 24dp
  • Màu: #000000
  • Độ mờ: 100%

Trình hướng dẫn Asset Studio cho thấy các dữ liệu đầu vào được đề cập trong bước này

  1. Sau đó, trong gói screen, hãy tạo một tệp có tên DetailScreen.kt (bên cạnh tệp MainScreen.kt hiện tại) và thêm đoạn mã sau:

DetailScreen.kt

class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {

    override fun onGetTemplate(): Template {
        val place = PlacesRepository().getPlace(placeId)
            ?: return MessageTemplate.Builder("Place not found")
                .setHeaderAction(Action.BACK)
                .build()

        val navigateAction = Action.Builder()
            .setTitle("Navigate")
            .setIcon(
                CarIcon.Builder(
                    IconCompat.createWithResource(
                        carContext,
                        R.drawable.baseline_navigation_24
                    )
                ).build()
            )
            // Only certain intent actions are supported by `startCarApp`. Check its documentation
            // for all of the details. To open another app that can handle navigating to a location
            // you must use the CarContext.ACTION_NAVIGATE action and not Intent.ACTION_VIEW like
            // you might on a phone.
            .setOnClickListener {  carContext.startCarApp(place.toIntent(CarContext.ACTION_NAVIGATE)) }
            .build()

        return PaneTemplate.Builder(
            Pane.Builder()
                .addAction(navigateAction)
                .addRow(
                    Row.Builder()
                        .setTitle("Coordinates")
                        .addText("${place.latitude}, ${place.longitude}")
                        .build()
                ).addRow(
                    Row.Builder()
                        .setTitle("Description")
                        .addText(place.description)
                        .build()
                ).build()
        )
            .setTitle(place.name)
            .setHeaderAction(Action.BACK)
            .build()
    }
}

Hãy đặc biệt chú ý đến cách tạo navigateAction — lệnh gọi đến startCarApp trong OnClickListener của nó là yếu tố chính để tương tác với các ứng dụng khác trên Android Auto và Android Automotive OS.

Bây giờ đã có hai loại màn hình, đã đến lúc thêm chức năng đi theo chỉ dẫn giữa chúng! Chức năng đi theo chỉ dẫn trong Thư viện Ứng dụng cho Ô tô sẽ sử dụng mô hình ngăn xếp với các lệnh thêm (push) và đóng (pop), lý tưởng cho các luồng tác vụ đơn giản phù hợp để hoàn tất trong khi lái xe.

Một sơ đồ thể hiện cách hoạt động của chức năng đi theo chỉ dẫn trong ứng dụng với Thư viện Ứng dụng cho Ô tô. Ở bên trái, có một ngăn xếp chỉ có MainScreen. Giữa ngắn xếp đó và ngăn xếp ở giữa là một mũi tên có nhãn &quot;Push DetailScreen&quot; (&quot;Thêm DetailScreen&quot;). Ngăn xếp ở giữa có một DetailScreen nằm trên MainScreen hiện tại. Giữa ngăn xếp ở giữa và ngăn xếp bên phải có một mũi tên có nhãn &quot;Pop&quot; (&quot;Đóng&quot;). Ngăn xếp bên phải giống như ngăn xếp bên trái, chỉ có MainScreen.

  1. Để điều hướng từ một trong các mục danh sách trên MainScreen đến DetailScreen cho mục đó, hãy thêm đoạn mã sau:

MainScreen.kt

Row.Builder()
    ...
    .setOnClickListener { screenManager.push(DetailScreen(carContext, it.id)) }
    ...

Việc điều hướng ngược lại từ DetailScreen về MainScreen đã được xử lý vì setHeaderAction(Action.BACK) được gọi khi tạo PaneTemplate hiển thị trên DetailScreen. Khi người dùng nhấp vào hành động trên tiêu đề, ứng dụng lưu trữ sẽ xử lý việc xoá màn hình hiện tại ra khỏi ngăn xếp cho bạn, nhưng ứng dụng của bạn có thể ghi đè hành vi này nếu muốn.

  1. Hãy chạy ứng dụng để xem DetailScreen và chức năng đi theo chỉ dẫn trong ứng dụng hoạt động như thế nào!

11. Cập nhật nội dung trên màn hình

Thường thì bạn muốn cho phép người dùng tương tác với màn hình và thay đổi trạng thái của các thành phần trên màn hình đó. Để minh hoạ cách thực hiện điều này, bạn sẽ tạo chức năng cho phép người dùng chuyển đổi qua lại giữa yêu thích và bỏ yêu thích cho một địa điểm trên DetailScreen.

  1. Trước tiên, hãy thêm một biến cục bộ isFavorite chứa trạng thái. Trong ứng dụng thực, dữ liệu này phải được lưu trữ dưới dạng một phần của lớp dữ liệu, nhưng một biến cục bộ là đủ cho mục đích minh hoạ.

DetailScreen.kt

class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
    private var isFavorite = false
    ...
}
  1. Tiếp theo, hãy nhấp chuột phải vào thư mục res trong mô-đun :common:car-app-service, nhấp vào New > Vector Asset (Mới > Thành phần vectơ), rồi tạo biểu tượng yêu thích bằng cách sử dụng cấu hình sau:
  • Loại thành phần: Clip art
  • Tên: baseline_favorite_24
  • Hình mẫu: favorite
  • Kích thước: 24dp x 24dp
  • Màu: #000000
  • Độ mờ: 100%

Trình hướng dẫn Asset Studio cho thấy các dữ liệu đầu vào được đề cập trong bước này

  1. Sau đó, trong DetailsScreen.kt, hãy tạo một ActionStrip cho PaneTemplate.

Các thành phần giao diện người dùng ActionStrip được đặt ở hàng tiêu đề đối diện với tiêu đề và là lý tưởng để sử dụng cho các hành động cấp hai và cấp ba. Vì đi theo chỉ dẫn là hành động chính được thực hiện trên DetailScreen, nên việc đặt Action là yêu thích hoặc bỏ yêu thích trong ActionStrip là một cách hiệu quả để tạo kết cấu cho màn hình.

DetailScreen.kt

val navigateAction = ...

val actionStrip = ActionStrip.Builder()
    .addAction(
        Action.Builder()
            .setIcon(
                CarIcon.Builder(
                    IconCompat.createWithResource(
                        carContext,
                        R.drawable.baseline_favorite_24
                    )
                ).setTint(
                    if (isFavorite) CarColor.RED else CarColor.createCustom(
                        Color.LTGRAY,
                        Color.DKGRAY
                    )
                ).build()
            )
            .setOnClickListener {
                isFavorite = !isFavorite
            }.build()
    )
    .build()

...

Có hai phần cần quan tâm ở đây:

  • CarIcon được tô màu tuỳ theo trạng thái của mục.
  • setOnClickListener dùng để phản ứng với hoạt động đầu vào từ người dùng và chuyển đổi trạng thái yêu thích.
  1. Đừng quên gọi hàm setActionStrip trên PaneTemplate.Builder để sử dụng!

DetailScreen.kt

return PaneTemplate.Builder(...)
    ...
    .setActionStrip(actionStrip)
    .build()
  1. Bây giờ, hãy chạy ứng dụng và xem điều gì xảy ra:

DetailScreen hiển thị. Người dùng đang nhấn vào biểu tượng yêu thích nhưng biểu tượng này không đổi màu như dự kiến.

Thật thú vị... có vẻ như giao diện người dùng không cập nhật mặc dù các lượt nhấp đang diễn ra.

Điều này là do Thư viện Ứng dụng cho Ô tô có khái niệm làm mới (refresh). Để hạn chế sự phân tâm của người lái xe, việc làm mới nội dung trên màn hình có một số hạn chế nhất định (tuỳ theo mẫu được hiển thị) và mỗi lần làm mới phải được mã nguồn của chính bạn yêu cầu rõ ràng bằng cách gọi phương thức invalidate của lớp Screen. Việc chỉ cập nhật một số trạng thái được tham chiếu trong onGetTemplate là không đủ để cập nhật giao diện người dùng.

  1. Để khắc phục vấn đề này, hãy cập nhật OnClickListener như sau:

DetailScreen.kt

.setOnClickListener {
    isFavorite = !isFavorite
    // Request that `onGetTemplate` be called again so that updates to the
    // screen's state can be picked up
    invalidate()
}
  1. Chạy lại ứng dụng để xem màu của biểu tượng trái tim được cập nhật sau mỗi lượt nhấp!

DetailScreen hiển thị. Người dùng đang nhấn vào biểu tượng yêu thích và biểu tượng này đổi màu đúng như dự kiến.

Và thế là bạn đã có một ứng dụng cơ bản được tích hợp tốt với cả Android Auto và Android Automotive OS!

12. Xin chúc mừng

Bạn đã tạo thành công ứng dụng đầu tiên của mình bằng Thư viện Ứng dụng cho Ô tô. Bây giờ là lúc áp dụng kiến thức đã học vào ứng dụng của riêng bạn!

Như đề cập trước đó, hiện tại thì chỉ một số ứng dụng nhất định thuộc danh mục ứng dụng được tạo bằng Thư viện Ứng dụng cho Ô tô mới có thể được gửi tới Cửa hàng Play. Nếu ứng dụng của bạn là ứng dụng đi theo chỉ dẫn, ứng dụng địa điểm yêu thích (POI) (như ứng dụng mà bạn xử lý trong lớp học lập trình này) hoặc ứng dụng Internet của vạn vật (IOT), thì bạn có thể bắt đầu xây dựng ứng dụng ngay hôm nay để xuất bản chính thức ứng dụng trên cả hai nền tảng.

Mỗi năm chúng tôi đều bổ sung danh mục ứng dụng mới. Do đó, kể cả khi bạn chưa áp dụng ngay được những gì vừa học, thì sau này cũng đừng quên kiểm tra lại, vì có thể đến thời điểm mở rộng ứng dụng của bạn sang cho ô tô!

Những điều nên thử

  • Cài đặt trình mô phỏng của nhà sản xuất thiết bị gốc (ví dụ: trình mô phỏng Polestar 2) và xem cách mà những chế độ tuỳ chỉnh của nhà sản xuất thiết bị gốc có thể thay đổi giao diện của ứng dụng Thư viện Ứng dụng cho Ô tô trên Android Automotive OS. Hãy lưu ý rằng không phải trình mô phỏng nào của nhà sản xuất thiết bị gốc cũng hỗ trợ ứng dụng sử dụng Thư viện Ứng dụng cho Ô tô.
  • Tham khảo ứng dụng mẫu Showcase minh hoạ đầy đủ chức năng của Thư viện Ứng dụng cho Ô tô.

Tài liệu đọc thêm

Tài liệu tham khảo