使用車輛專用 Android App Library

車輛專用 Android App Library 可讓您使用導航、搜尋點和物聯網 (IoT) 應用程式傳送到車上 我們提供一系列專用範本,協助駕駛人分心 也會妥善保護車輛螢幕等細節 以及輸入模式

本指南將概略說明程式庫的主要功能與概念,以及 會逐步引導您設定基本應用程式。

事前準備

  1. 查看行車使用者體驗設計指南 涵蓋 Car App Library 的頁面
  2. 請參閱下文的重要詞彙及概念 專區。
  3. 熟悉 Android Auto 系統 使用者介面Android Automotive OS 設計
  4. 查看版本資訊
  5. 查看範例

重要詞彙與概念

模型和範本
使用者介面以模型物件圖表表示 依照範本允許,以不同方式排列在一起 。範本是模型的子集,可在這些模型中做為根層級 圖表。模型會包含要在 文字、圖片形式,以及設定 這類資訊的視覺外觀 (例如文字顏色或圖片) 大小。主機將模型轉換為符合 駕駛人分心等級的標準,並會妥善處理各項細節,例如 例如車輛螢幕因素和輸入模式
舉辦派對
主機是後端元件,負責實作所列功能 ,以便您的應用程式可在車上執行。 主機的職責包括發掘應用程式及管理 也就是將模型轉換為檢視畫面,並通知應用程式 以及使用者的互動方式在行動裝置上,這個主機是由 Android 實作 自動、在 Android Automotive OS 中,這個主機已安裝為系統應用程式。
範本限制
不同的範本會強制限制模型的內容。適用對象 例如,清單範本對可儲存的項目數量設有限制 都顯示給使用者範本也設有限制 形成任務的流程舉例來說,應用程式只能推送 最多可在畫面堆疊中加入五個範本詳情請見 範本限制
Screen
Screen 是由 應用程式實作程式庫,管理呈現給 內容。Screen 具有 生命週期,並為應用程式提供 傳送要在螢幕顯示時顯示的範本。 您也能推送 Screen 個執行個體 並彈出 Screen 堆疊 確保遵循 範本流程限制
CarAppService
CarAppService是 應用程式的抽象 Service 類別 必須實作並匯出,才能由主機尋找及管理。 應用程式的CarAppService為 會負責驗證主機連線是否可信任 createHostValidator 然後提供 Session 每個連線的執行個體數量 onCreateSession
Session

Session 是一種抽象類別, 您的應用程式必須使用 CarAppService.onCreateSession。 這可做為在車輛螢幕上顯示資訊的進入點。這項服務 設有生命週期 應用程式目前在車輛螢幕上的狀態,例如應用程式 已隱藏或隱藏。

Session 啟動時,例如 應用程式初次啟動時,初始的主機要求 Screen,以使用 onCreateScreen敬上 方法。

安裝 Car App Library

查看 Jetpack 程式庫 發布頁面 瞭解如何將程式庫新增至應用程式。

設定應用程式的資訊清單檔案

建立車用應用程式前,請先設定應用程式的 資訊清單檔案,如下所示。

宣告您的 CarAppService

主機會透過 CarAppService 實作。個人中心 在資訊清單中宣告這項服務,讓主機探索並連線 導入您的應用程式

您也必須在 應用程式的 <category> 元素 意圖篩選器請參閱 支援的應用程式類別 (用來指定 這個元素

下列程式碼片段說明如何為 一些興趣應用程式:

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

支援的應用程式類別

新增下列一或多個類別以宣告您的應用程式類別 您依上述說明宣告 CarAppService 時,意圖篩選器中的值 在上一節中:

  • androidx.car.app.category.NAVIGATION:提供即時路線導航功能的應用程式 導航路線 請參閱「建構車輛專用導航應用程式」一文 ,進一步瞭解這個類別的其他文件。
  • androidx.car.app.category.POI:提供相關功能的應用程式 例如停車位、充電站和 加油站。退房日 建構車輛專用搜尋點應用程式 更多與這個類別相關的說明文件
  • androidx.car.app.category.IOT:應用程式可讓使用者根據相關主題 在車內對已連結裝置上的動作執行動作。退房日 建構車輛專用物聯網應用程式 更多與這個類別相關的說明文件
,瞭解如何調查及移除這項存取權。

請參閱「車用 Android 應用程式品質指南」 各類別及其所屬應用程式的詳細說明。

指定應用程式名稱和圖示

您必須指定應用程式名稱和圖示,讓主機能用來代表 開始定義應用程式

您可以使用以下項目,指定要用於代表應用程式的應用程式名稱和圖示: label 和 您的 icon 屬性 CarAppService

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

如未在 <service> 元素,主機 回復為指定值 <application> 元素。

設定自訂主題

如要設定車用應用程式的自訂主題,請新增 <meta-data> 元素 資訊清單檔案如下所示:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

然後宣告樣式資源 為您的自訂車輛應用程式主題設定下列屬性:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

車輛應用程式 API 級別

Car App Library 會定義自己的 API 級別,您可以知道 車上的範本主機支援程式庫功能。 如要擷取主機支援的最高 Car App API 級別,請使用 getCarAppApiLevel()敬上 方法。

在您的應用程式中,宣告應用程式支援的最低 Car App API 級別 AndroidManifest.xml 檔案:

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

詳情請參閱 RequiresCarApi敬上 註解,說明如何維持回溯相容性及宣告 使用某項功能所需的最低 API 級別。如要瞭解 需要等級,才能使用 Car App Library 的特定功能,請查看 參考文件 CarAppApiLevels

建立 CarAppService 和工作階段

您的應用程式需要擴充 CarAppService 類別並實作 其 onCreateSession。 方法,此方法會傳回 Session 目前與主機連線相對應的執行個體:

Kotlin

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Java

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

Session 執行個體負責 傳回 Screen 例項,藉此使用 。

Kotlin

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Java

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

處理車輛應用程式需要從非螢幕啟動的情況 那麼應用程式的首頁或到達畫面 (例如處理深層連結) 即可 預先種子的螢幕返回堆疊 ScreenManager.push敬上 之前,從 onCreateScreen。 預先播映功能可讓使用者從第一個畫面返回上一個畫面 顯示您的應用程式。

建立開始畫面

您可以定義 Screen 類別並實作其類別 onGetTemplate 方法,以便傳回 Template 執行個體代表的 要在車輛螢幕上顯示的 UI 狀態。

下列程式碼片段說明如何宣告 Screen採用 將「PaneTemplate」範本套用至 顯示簡單的「Hello World!」字串:

Kotlin

class HelloWorldScreen(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()
    }
}

Java

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

CarContext 類別

CarContext 類別是 ContextWrapper 子類別 您的 SessionScreen 執行個體。這項服務提供存取權 例如 ScreenManager 用於管理 畫面堆疊;這個 AppManager (適用於一般應用程式相關) 功能,例如存取繪圖地圖Surface 物件; 和 NavigationManager 即時路況導航應用程式用來傳達導航資訊 中繼資料和其他 導航相關 活動

請參閱存取導覽 範本 導航應用程式可用的程式庫功能完整清單。

CarContext 還提供其他 例如讓您透過設定載入可繪項目資源 使用意圖在車內啟動應用程式 並信號指出應用程式是否應以深色主題顯示地圖。

實作畫面導覽

應用程式通常會顯示多種不同的畫面,且每個畫面可能都使用 使用者可以在互動時瀏覽的不同範本 在畫面上顯示的介面

ScreenManager 類別提供 螢幕堆疊,用於推送可自動彈出的畫面 使用者在車輛螢幕上選取返回按鈕,或使用硬體背面時 按鈕。

下列程式碼片段說明如何在訊息範本中新增返回動作: 以及會促使使用者選取新畫面的動作:

Kotlin

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Java

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

Action.BACK 物件是 標準 Action,系統會自動 叫用 ScreenManager.pop。 您可以使用 OnBackPressedDispatcher敬上 執行個體 CarContext

為確保在行車期間安全使用應用程式,畫面堆疊的 最高 與五台螢幕結合請參閱範本限制 一節。

重新整理範本內容

應用程式可以要求 Screen,以呼叫 Screen.invalidate 方法。 主機隨後又呼叫了應用程式的 Screen.onGetTemplate敬上 方法,擷取含有新內容的範本。

重新整理 Screen 時, 請務必瞭解範本中可更新的具體內容 因此主機不會將新範本計入範本配額。 詳情請參閱「範本限制」一節。

建議你妥善規劃畫面,確保畫面呈現單一畫面 對應的 Screen 和類型 並透過 onGetTemplate 實作傳回的範本

繪製地圖

使用以下範本的導航和搜尋點 (POI) 應用程式,可以 透過存取 Surface 繪製地圖:

Template 範本權限 類別指南
NavigationTemplate androidx.car.app.NAVIGATION_TEMPLATES 導覽
MapWithContentTemplate androidx.car.app.NAVIGATION_TEMPLATES
androidx.car.app.MAP_TEMPLATES
導航搜尋點
MapTemplate (已淘汰) androidx.car.app.NAVIGATION_TEMPLATES 導覽
PlaceListNavigationTemplate (已淘汰) androidx.car.app.NAVIGATION_TEMPLATES 導覽
RoutePreviewNavigationTemplate (已淘汰) androidx.car.app.NAVIGATION_TEMPLATES 導覽

宣告表面權限

除了應用程式使用範本所需的權限外, 應用程式必須在應用程式內宣告 androidx.car.app.ACCESS_SURFACE 權限 AndroidManifest.xml 檔案可存取介面:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
  ...
</manifest>

存取介面

如要存取主機提供的 Surface,您必須 SurfaceCallback,並提供 新增至 AppManager 汽車維修服務。目前的Surface會傳遞至 SurfaceCallback 中的 SurfaceContainer 參數 onSurfaceAvailable()onSurfaceDestroyed() 回呼。

Kotlin

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Java

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

瞭解平面的可見區域

主機可在地圖上層為範本繪製使用者介面元素。主機會傳遞不保證表面區域的區域 透過呼叫 SurfaceCallback.onVisibleAreaChanged敬上 方法。此外,為了盡量減少變更,主機會透過最小矩形呼叫 SurfaceCallback.onStableAreaChanged 方法,這種矩形一律會根據目前的範本顯示。

舉例來說,如果導航應用程式使用 NavigationTemplate敬上 在畫面頂端有動作列,即可隱藏動作列 當使用者有一段時間未與畫面互動來請求其他報表時 也就是地圖空間在此情況下,系統會回呼 onStableAreaChanged 並 具有相同矩形的 onVisibleAreaChanged。操作列隱藏時 只有較大的區域呼叫 onVisibleAreaChanged。如果使用者 與螢幕互動,然後再次呼叫 onVisibleAreaChanged 第一個矩形

支援深色主題

應用程式必須在 Surface 例項中,以適當的深色重新繪製地圖 如下所述: 車用 Android 應用程式品質

如要判斷是否應繪製深色地圖,可以使用 CarContext.isDarkMode 方法。每當深色主題狀態變更時,您就會收到 Session.onCarConfigurationChanged

允許使用者與地圖互動

使用以下範本時,您可以新增使用者支援服務 例如讓您可以瀏覽地圖的不同部分 以及縮放和平移功能

Template 開始支援互動功能的 Car App API 級別
NavigationTemplate 2
PlaceListNavigationTemplate (已淘汰) 4
RoutePreviewNavigationTemplate (已淘汰) 4
MapTemplate (已淘汰) 5 (導入範本)
MapWithContentTemplate 7 (導入範本)

實作互動回呼

SurfaceCallback 介面 有多種回呼方法可實作為建構的地圖添加互動性 換成上一節的範本:

互動 SurfaceCallback 方法 開始支援的 Car App API 級別
輕觸 onClick 5
雙指撥動縮放 onScale 2
單點觸控拖曳 onScroll 2
單點觸控滑動 onFling 2
輕觸兩下 onScale (由範本主機決定縮放比例係數) 2
平移模式的旋轉自動提醒 onScroll (由範本主機決定距離係數) 2

新增地圖動作區域

這些範本可包含地圖操作列,用於地圖相關動作,例如: 縮放畫面、重新置中、顯示指南針等 決定要顯示的內容地圖動作列最多可有四個純圖示按鈕 就能在不影響工作深度的情況下重新整理。處於閒置狀態時隱藏起來 就會重新顯示為啟用狀態

如要接收地圖互動回呼, 「必須」在地圖動作列中加入 Action.PAN 按鈕。當使用者 按下平移按鈕,主機會進入平移模式,如下所述 專區。

如果應用程式忽略地圖動作列中的 Action.PAN 的按鈕,就無法從 SurfaceCallback 方法收到使用者輸入內容,而且主機會結束先前啟用的平移模式。

如果是觸控螢幕,畫面上不會顯示平移按鈕。

瞭解平移模式

在平移模式下,使用者透過非觸控輸入裝置 (例如旋轉控制器和觸控板) 輸入的內容,會由範本主機轉譯成適當的 SurfaceCallback 方法。使用 NavigationTemplate.Builder 中的 setPanModeListener 方法,可在使用者進入或結束平移模式時做出反應。使用者處於平移模式時,主機可隱藏範本中的其他 UI 元件。

與使用者互動

您的應用程式可以透過與行動應用程式類似的模式與使用者互動。

處理使用者輸入內容

應用程式可以藉由傳遞適當的事件監聽器給 支援生成式 AI 模型下列程式碼片段說明如何建立 Action 模型,可設定 符合此條件的 OnClickListener 會傳回應用程式程式碼定義的方法:

Kotlin

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Java

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

接著,onClickNavigate 方法可以啟動 預設車用導航應用程式 方法是使用 CarContext.startCarApp。 方法:

Kotlin

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Java

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

如要進一步瞭解如何啟動應用程式,包括 ACTION_NAVIGATE 意圖,請參閱「使用意圖啟動車輛應用程式」 專區。

請引導使用者繼續操作, 的行動裝置互動方式。 您可以使用 ParkedOnlyOnClickListener敬上 實際執行這些動作如果不停車,主辦人會顯示 告知使用者在此情況下,該動作不允許該動作。如果車輛 才能正常執行下列程式碼片段說明如何 使用ParkedOnlyOnClickListener 如何在行動裝置上開啟設定畫面:

Kotlin

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Java

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

顯示通知

傳送到行動裝置的通知只會顯示在符合以下條件的情況下,於車輛螢幕上顯示 擴充功能會使用 CarAppExtender。 部分通知屬性,例如內容標題、文字、圖示和動作 可在 CarAppExtender 中設定,並覆寫通知屬性 。

下列程式碼片段說明如何傳送通知到有 顯示的標題與行動裝置上顯示的標題不同:

Kotlin

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Java

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

通知可能會影響使用者介面的以下部分:

  • 系統可能會向使用者顯示抬頭通知 (HUN)。
  • 您可在通知中心新增一個項目,並視需要顯示標記 就可以在邊欄看到
  • 如果是導航應用程式,通知可能會在邊欄小工具中顯示為: 描述 即時路線導航通知
,瞭解如何調查及移除這項存取權。

您可以選擇如何設定應用程式通知,對不同的 和這些使用者介面元素搭配使用 的 CarAppExtender敬上 說明文件。

如果 NotificationCompat.Builder.setOnlyAlertOnce敬上 系統會使用 true 的值呼叫,而高優先順序的通知會顯示為 只能執行一次。

如要進一步瞭解如何設計車用應用程式的通知,請參閱 Google Design for Driver 指南 通知:

顯示浮動式訊息

您的應用程式可以使用以下功能顯示浮動式訊息: CarToast,如以下程式碼片段所示:

Kotlin

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Java

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

要求權限

如果您的應用程式需要存取受限資料或動作 (例如: 位置—Android 標準規則 權限 或如要申請權限,請使用 CarContext.requestPermissions()敬上 方法。

使用 CarContext.requestPermissions(),而非 標準 Android API 因此您不必自行啟動 Activity 建立權限對話方塊此外,您可以在 不必建立 Android Auto 和 Android Automotive OS 平台導向的流程

設定 Android Auto 權限對話方塊的樣式

Android Auto 會在手機上顯示使用者的權限對話方塊。 根據預設,對話方塊後不會有任何背景。如何設定自訂的 請在背景中宣告車用應用程式主題 AndroidManifest.xml 檔案並設定 carPermissionActivityLayout 屬性 打造最適合你的車用應用程式主題

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

然後,設定車輛應用程式主題的 carPermissionActivityLayout 屬性:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

使用意圖啟動車用應用程式

您可以呼叫 CarContext.startCarApp敬上 方法執行下列任一動作:

以下範例說明如何建立含有 會開啟應用程式,並顯示預訂停車位詳細資料的畫面。 您可以利用包含 PendingIntent 包裝 吸引使用者註意

Kotlin

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Java

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

此外,應用程式也必須宣告 BroadcastReceiver,即 當使用者在 通知介面和叫用 CarContext.startCarApp。 具有包含資料 URI 的意圖:

Kotlin

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Java

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

最後, Session.onNewIntent敬上 方法,藉由推送停車預訂畫面來處理這項意圖 位置 (如果不是位於最上層):

Kotlin

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Java

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

詳情請參閱「顯示通知」一節 瞭解如何處理車用應用程式通知。

範本限制

主機將指定工作可顯示的範本數量限制為上限 ,且最後一個範本必須是下列其中一種類型:

請注意,這項限制適用於範本數量,不適用於 堆疊中的 Screen 執行個體。適用對象 例如,如果應用程式在畫面 A 中傳送兩個範本,然後推送螢幕 B,現在可以再傳送三個範本。或者,如果每個畫面是結構化的 傳送單一範本,應用程式就能將五個畫面執行個體推送到 ScreenManager 堆疊。

這些限制有一些特殊情況:範本重新整理、返回和 重設作業

範本重新整理作業

某些內容更新不會計入範本限制。一般來說 應用程式推送相同類型的新範本,且包含 與前一個範本的主要內容相同,新範本並不是 也會計入配額例如,更新某個資料列中某列的切換狀態 ListTemplate 不會計入 依據配額詳情請參閱個別範本的說明文件 哪類內容更新可視為重新整理。

返回作業

如要在工作中啟用子流程,主機會偵測應用程式何時彈出 來自 ScreenManager 堆疊和更新的 Screen 根據應用程式即將使用的範本數量 計算剩餘的配額 反向排序

舉例來說,如果應用程式在畫面 A 中傳送兩個範本, 畫面 B 並另外傳送兩個範本,但應用程式只剩一項配額。如果 應用程式回到畫面 A 後,主機會將配額重設為 3,因為 應用程式會回溯使用兩個範本

請注意,當您彈出至螢幕時,應用程式必須傳送 以及該畫面最後傳送的相同類型傳送任何其他郵件 範本類型會導致錯誤。不過,只要型別仍 在返回作業期間相同,應用程式可以自由修改 而且不會影響配額

重設作業

某些範本具有特殊語意,代表工作的結尾。適用對象 舉例來說, NavigationTemplate敬上 應該會持續顯示在畫面上,而且會重新整理新的檢視畫面 方便使用者掌握使用情況的交通路線達到以下任一情況時 主機會重設範本配額, 是新任務的第一步這可讓應用程式開始新的工作。 請參閱個別範本的說明文件,瞭解哪些範本會觸發重設作業 。

如果主機收到透過通知動作啟動應用程式的意圖,或 也會重設配額。這個機制可讓應用程式 應用程式必須從通知啟動新的工作流程,即使應用程式 進入前景並鎖定位置。

詳情請參閱「顯示通知」一節 如何在車輛螢幕中顯示應用程式通知。詳情請參閱 「使用意圖啟動車用應用程式」一節將說明如何操作 透過通知動作啟動應用程式

連線 API

您可以判斷應用程式是否在 Android Auto 或 Android 上執行 使用 CarConnection API 可 在執行階段擷取連線資訊

例如,在車用應用程式的 Session 中初始化 CarConnection,並 訂閱 LiveData 的更新:

Kotlin

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Java

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

在觀察器中,您可以回應連線狀態的變更:

Kotlin

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Java

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

限制 API

不同車輛可能會允許使用不同數量 Item 執行個體 指定時間使用 ConstraintManager敬上 在執行階段檢查內容限制,並設定合適的項目數量

首先,從 CarContext 取得 ConstraintManager

Kotlin

val manager = carContext.getCarService(ConstraintManager::class.java)

Java

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

接著,您可以查詢已擷取的 ConstraintManager 物件來取得相關 內容限制。舉例來說,如要取得可在 格線、呼叫 getContentLimit敬上 同時 CONTENT_LIMIT_TYPE_GRID:

Kotlin

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Java

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

新增登入流程

如果您的應用程式為使用者提供登入體驗,您可以使用下列範本: SignInTemplateLongMessageTemplate 使用 Car App API 級別 2 以上,即可處理在 車用運算主機

如要建立 SignInTemplate,請定義 SignInMethod。車輛 應用程式程式庫目前支援下列登入方式:

舉例來說,如要實作收集使用者密碼的範本,首先應從 建立InputCallback 來處理及驗證使用者輸入內容:

Kotlin

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Java

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

必須為 InputSignInMethod Builder 設定 InputCallback

Kotlin

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Java

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

最後,使用新的 InputSignInMethod 建立 SignInTemplate

Kotlin

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Java

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

使用 AccountManager

設有驗證機制的 Android Automotive OS 應用程式 AccountManager 的原因如下:

  • 提供更優質的使用者體驗和簡便的帳戶管理功能:使用者可輕鬆管理所有應用程式 透過系統設定的帳戶選單 (包括登入相關帳戶) 存取帳戶 然後再登出
  • 「訪客」體驗:由於車輛是共用裝置,因此原始設備製造商 (OEM) 可以 訪客體驗,無法新增帳戶。

新增文字字串變化版本

不同大小的車輛螢幕顯示的文字量可能不同。使用 Car App API 第 2 級以上,您可以指定多個文字字串變化版本,以達到最佳效果 配合螢幕大小如要瞭解文字變化版本的適用地區,請查看範本並 採用 CarText 的元件

您可以使用以下內容,在 CarText 中新增文字字串變化版本: CarText.Builder.addVariant() 方法:

Kotlin

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Java

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

接著,您可以使用這個 CarText,例如 GridItem

Kotlin

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Java

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

按照偏好由高到低的順序新增字串,例如從最長到 最短。主機會根據 以及車輛螢幕的可用空間

新增資料列的內嵌 CarIcons

您可以加入文字內嵌圖示,使用 CarIconSpan。 詳情請參閱 CarIconSpan.create敬上 ,進一步瞭解如何建立這些時距。詳情請見 Spantastic 使用 Spans 建立文字樣式,概略瞭解 Span 的文字樣式運作方式。

Kotlin

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Java

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

車用硬體 API

從 Car App API 級別 3 開始,車輛應用程式程式庫會提供您熟悉的 API 存取車輛屬性和感應器

需求條件

如要搭配使用 API 與 Android Auto,請先在以下位置新增依附元件: 將 androidx.car.app:app-projected 新增至 Android 的 build.gradle 檔案 自動模組。如果是 Android Automotive OS,請新增依附元件 將 androidx.car.app:app-automotive 新增至 Android 的 build.gradle 檔案 Automotive OS 模組。

此外,在 AndroidManifest.xml 檔案中,您還需要 宣告相關權限 要求要使用的車輛資料。請注意,這些權限也必須 使用者授予您的權限。您可以使用 Android Auto 和 Android Automotive OS 上的相同程式碼,而非 不必建立獨立於平台的流程不過,您必須具備必要權限 有些則不同

汽車資訊

下表說明 CarInfo API 和 如要使用這些元件,您需要具備下列權限:

方法 屬性 Android Auto 權限 Android Automotive OS 權限 開始支援的 Car App API 級別
fetchModel 廠牌、型式、年份 android.car.permission.CAR_INFO 3
fetchEnergyProfile 電動車連接器類型、燃料類型 com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

這項資料僅適用於部分 Android Automotive OS 車輛 搭載 API 30 以上版本

外觀尺寸 android.car.permission.CAR_INFO 7
addTollListener
removeTollListener
付費卡狀態、收費卡類型 3
addEnergyLevelListener
removeEnergyLevelListener
電池電量, 油量, 油量低, 剩餘電量 com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY,
android.car.permission.CAR_ENERGY_PORTS,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addSpeedListener
removeSpeedListener
原始速度、顯示速度 (顯示在車輛的儀表板螢幕上) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addMileageListener
removeMileageListener
里程表距離 com.google.android.gms.permission.CAR_MILEAGE Android Automotive OS 不會針對透過 Play 商店安裝的應用程式提供這項資料。 3

舉例來說,如要取得剩餘的範圍,請將 CarInfo 物件,然後 建立並註冊 OnCarDataAvailableListener

Kotlin

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Java

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

請勿假設車輛隨時都能取得資料。 如果收到錯誤訊息,請檢查 狀態: 供您進一步瞭解自己要求的資料為何可能 未擷取。詳情請參閱 參考文件 完整的 CarInfo 類別定義

汽車感測器

CarSensors 類別 存取車輛的加速計、陀螺儀、指南針和 位置資料。這些值的可用性可能取決於 原始設備製造商 (OEM)。加速計、陀螺儀和指南針的資料格式是 就如同 SensorManager API:例如: 檢查車輛方向:

Kotlin

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Java

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

如要存取車輛的位置資料,您還需要宣告並要求 android.permission.ACCESS_FINE_LOCATION權限。

測試

如要在 Android Auto 上測試時模擬感應器資料,請參閱 感應器感應器 「設定」部分 電腦版車用運算主機指南。在 Android 上測試時模擬感應器資料 Automotive OS,請參閱「Emulate 硬體 Android 上的狀態 Automotive OS 模擬器指南。

CarAppService、工作階段和螢幕生命週期

SessionScreen 類別會導入 LifecycleOwner 介面。阿斯 使用者與應用程式互動,以及 SessionScreen 物件的生命週期 叫用回呼,如下圖所示。

CarAppService 和工作階段的生命週期

圖 1. Session 生命週期。

如需完整詳細資料,請參閱 Session.getLifecycle敬上 方法。

畫面的生命週期

圖 2. Screen 生命週期。

如需完整詳細資料,請參閱 Screen.getLifecycle 方法,增加圍繞地圖邊緣的邊框間距。

透過汽車麥克風錄影

使用應用程式的 CarAppServiceCarAudioRecord API、 您可以授權應用程式存取使用者的車用麥克風。使用者必須授予 應用程式權限。您的應用程式可以錄製並 並在應用程式內處理使用者的輸入內容。

錄音權限

錄製任何音訊前,您必須先宣告其在 AndroidManifest.xml,並要求使用者授予權限。

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

你必須取得在執行階段錄製的權限。請參閱要求 權限一節,進一步瞭解如何要求 授予這項權限。

錄音

使用者授予錄音權限後,你就能錄下音訊和處理程序 像是從錄音中移除

Kotlin

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

音訊焦點

從汽車麥克風錄音時,請先取得音訊 將重點放在 確保播放中的媒體已停止。萬一你失去音訊焦點 停止錄製。

以下範例說明如何取得音訊焦點:

Kotlin

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

測試程式庫

車輛專用 Android 測試 程式庫 可用來在測試環境中驗證應用程式行為的類別。 舉例來說, SessionController敬上 可讓您模擬與主機的連線 ScreenTemplate

詳情請參閱 範例 查看使用範例

回報車輛專用 Android App Library 問題

如果發現資料庫有問題,請使用 Google Issue Tracker: 在問題範本中,請務必填寫所有必要資訊。

建立新問題

提交新問題之前,請確認該問題是否列在媒體庫版本中 通知或問題清單您可以透過以下方式訂閱問題並進行投票: 在追蹤程式中按一下問題的星號。詳情請參閱訂閱問題一文。