車輛專用 Android App Library 可讓您將導航、搜尋點和物聯網 (IOT) 應用程式移至車上。為此,我們會提供一組範本,專為滿足駕駛人分心等級的標準設計,並會謹慎處理各種細節,例如各種車輛螢幕因素和輸入模式。
本指南提供程式庫的主要功能和概念總覽,並逐步引導您設定簡易應用程式的程序。如需完整的逐步操作說明,請參閱「瞭解 Car App Library 基礎知識程式碼研究室」。
事前準備
- 查看涵蓋 Car App Library 的「Design for Driving」頁面
- 導航應用程式和其他行車相關應用程式類別總覽
- 「使用範本建構應用程式」總覽
- 建構模塊涵蓋範本和範本元件
- 示範常見的使用者體驗模式的範例流程
- 範本應用程式規定
- 請參閱下一節的重要詞彙與概念。
- 熟悉 Android Auto 系統 UI 和 Android Automotive OS 設計。
- 查看版本資訊。
- 查看範例。
重要詞彙與概念
- 模型與範本
- 使用者介面是以模型物件圖表呈現,這些物件可透過各種方式以不同方式排列,如其所屬的範本允許。範本是模型的子集,可以在這些圖形中做為根層級。模型包含要以文字和圖片的形式向使用者顯示的資訊,以及用於設定資訊視覺外觀各個面向 (例如文字顏色或圖片大小) 的屬性。主機會將模型轉換為檢視畫面,這些檢視畫面的設計旨在滿足駕駛人分心等級標準,並負責處理各種細節,例如各種車輛螢幕因素和輸入模式。
- 主機
- 主機是後端元件,可實作程式庫 API 提供的功能,讓應用程式可在車輛中執行。主機的責任範圍包括探索應用程式和管理其生命週期、將模型轉換為檢視畫面,以及通知應用程式使用者互動。在行動裝置上,這個主機是由 Android Auto 實作。在 Android Automotive OS 中,此主機會安裝為系統應用程式。
- 範本限制
- 不同的範本會強制執行模型內容限制。舉例來說,清單範本對使用者可顯示的項目數量設有限制。範本也設有限制,以連結這些範本以形成工作流程。舉例來說,應用程式最多只能將五個範本推送至畫面堆疊。詳情請參閱「範本限制」一節。
Screen
Screen
是程式庫提供的類別,應用程式會實作這些類別來管理向使用者顯示的使用者介面。Screen
具有生命週期,並提供可讓應用程式在畫面顯示時傳送範本的機制。也可以將Screen
執行個體推送和從Screen
堆疊推送和彈出,確保符合範本流程限制。CarAppService
CarAppService
是抽象的Service
類別,應用程式必須實作及匯出,主機才能探索及管理。應用程式的CarAppService
會負責使用createHostValidator
驗證主機連線是否可信任,隨後使用onCreateSession
為每個連線提供Session
執行個體。Session
Session
是抽象類別,應用程式必須使用CarAppService.onCreateSession
實作及傳回。這可做為在車輛螢幕上顯示資訊的進入點。它具有生命週期,可在車輛螢幕上通知應用程式目前的狀態,例如應用程式是否可見或隱藏。啟動
Session
(例如應用程式首次啟動時) 時,會使用onCreateScreen
方法要求初始Screen
顯示的主機。
安裝 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 應用程式品質」一文,進一步瞭解各個類別以及應用程式所屬的條件。
指定應用程式名稱和圖示
您必須指定應用程式名稱和圖示,主機可用來在系統 UI 中代表您的應用程式。
您可以使用 CarAppService
的 label
和 icon
屬性,指定用來代表應用程式的應用程式名稱和圖示:
...
<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>
Car App API 級別
Car App Library 會定義自己的 API 級別,方便您瞭解車輛上的範本主機支援哪些程式庫功能。如要擷取主機支援的最高 Car App API 級別,請使用 getCarAppApiLevel()
方法。
在 AndroidManifest.xml
檔案中宣告應用程式支援的最低車輛應用程式 API 級別:
<manifest ...>
<application ...>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1"/>
</application>
</manifest>
請參閱 RequiresCarApi
註解的說明文件,進一步瞭解如何維持回溯相容性,並宣告使用某項功能所需的最低 API 級別。如想瞭解使用 Car App Library 特定功能所需的 API 級別,請參閱 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
子類別,可供 Session
和 Screen
執行個體存取。這個程式庫提供車輛服務的存取權,例如用來管理畫面堆疊的 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
物件是會自動叫用 ScreenManager.pop
的標準 Action
。您可以使用 CarContext
提供的 OnBackPressedDispatcher
執行個體覆寫這個行為。
為確保行車期間安全使用應用程式,螢幕堆疊的深度上限為五個畫面。詳情請參閱「範本限制」一節。
重新整理範本內容
應用程式可以呼叫 Screen.invalidate
方法,要求無效的 Screen
內容。主機隨後會呼叫應用程式的 Screen.onGetTemplate
方法,使用新內容擷取範本。
重新整理 Screen
時,請務必瞭解範本中可更新的特定內容,這樣主機就不會將新範本計入範本配額。詳情請參閱「範本限制」一節。
建議您建構畫面結構,讓 Screen
和透過其 onGetTemplate
實作傳回的範本類型之間採用一對一對應。
與使用者互動
您的應用程式可以使用類似於行動應用程式的模式與使用者互動。
處理使用者輸入內容
應用程式可將適當的事件監聽器傳遞給支援這些事件監聽器的模型,藉此回應使用者輸入內容。下列程式碼片段說明如何建立 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
說明文件的說明,選擇如何將應用程式的通知設為影響每個使用者介面元素的通知。
如果使用 true
值呼叫 NotificationCompat.Builder.setOnlyAlertOnce
,則高優先順序的通知僅會顯示為 HUN 一次。
如要進一步瞭解如何設計車用應用程式的通知,請參閱 Google Design for Driving 的通知相關頁面。
顯示浮動式訊息
應用程式可以使用 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());
當使用者在通知介面中選取動作,並使用包含資料 URI 的意圖叫用 CarContext.startCarApp
時,應用程式也必須宣告用於處理意圖的 BroadcastReceiver
:
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())); } } }
如要進一步瞭解如何處理車輛應用程式的通知,請參閱「顯示通知」一節。
範本限制
主機限制了特定工作最多顯示的範本數量 (最多 5 個),最後一個範本必須是下列其中一種類型:
請注意,這項限制適用於範本數量,不適用於堆疊中的 Screen
執行個體數量。舉例來說,如果應用程式在畫面 A 中傳送兩個範本,然後推送畫面 B,現在就可以再傳送三個範本。或者,如果每個畫面都是傳送單一範本,則應用程式可以將五個畫面執行個體推送至 ScreenManager
堆疊。
這些限制有一些特殊情況:會重新整理範本,以及執行返回和重設作業。
範本重新整理次數
某些內容更新不會計入範本限制。一般來說,如果應用程式推送類型相同的新範本,且包含與先前範本相同的主要內容,新的範本就不會計入配額。舉例來說,更新 ListTemplate
中資料列的切換狀態不會計入配額。請參閱個別範本的說明文件,進一步瞭解哪些類型的內容更新可視為重新整理。
返回作業
為了在工作中啟用子流程,主機會偵測何時從 ScreenManager
堆疊彈出 Screen
,並根據應用程式返回的範本數量更新剩餘的配額。
舉例來說,如果應用程式在畫面 A 中傳送兩個範本,接著推送畫面 B 並傳送兩個範本,則應用程式剩餘的配額。如果應用程式接著彈回畫面 A,主機會將配額重設為 3,因為應用程式已經改回使用兩個範本。
請注意,當彈回畫面時,應用程式必須傳送與該畫面最後一個傳送的相同類型的範本。傳送任何其他範本類型都會導致錯誤。不過,只要類型在返回作業期間保持不變,應用程式就能自由修改範本內容,而不影響配額。
重設作業
某些範本具有特殊的語意,可代表任務的結尾。舉例來說,NavigationTemplate
是一個應留在螢幕上的檢視畫面,會以新的即時路線指示 (供使用者參考) 重新整理。當主機到達其中一個範本時,主機會重設範本配額,並將該範本視為新任務的第一步。這樣做可讓應用程式開始新的工作。請參閱個別範本的說明文件,瞭解哪些範本會在主機上觸發重設。
如果主機收到透過通知動作或啟動器啟動應用程式的意圖,系統也會重設配額。這項機制可讓應用程式從通知開始新的工作流程,即使應用程式已繫結並在前景執行,也會保持 true。
如要進一步瞭解如何在車輛螢幕上顯示應用程式通知,請參閱「顯示通知」一節。如要瞭解如何透過通知動作啟動應用程式,請參閱「透過意圖啟動車用應用程式」一節。
連線 API
您可以使用 CarConnection
API 在執行階段擷取連線資訊,判斷應用程式是在 Android Auto 或 Android Automotive OS 上執行。
舉例來說,在車用應用程式的 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
物件,瞭解相關內容限制。舉例來說,如要取得可在格狀檢視畫面中顯示的項目數量,請使用 CONTENT_LIMIT_TYPE_GRID
呼叫 getContentLimit
:
Kotlin
val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)
Java
int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);
新增登入流程
如果應用程式為使用者提供登入體驗,您可以使用 SignInTemplate
和 LongMessageTemplate
等範本搭配 Car App API 級別 2 以上級別,來處理車輛車用運算主機上的應用程式登入作業。
如要建立 SignInTemplate
,請定義 SignInMethod
。Car App Library 目前支援下列登入方式:
InputSignInMethod
用於使用者名稱/密碼登入。PinSignInMethod
用於 PIN 碼登入,可讓使用者使用車用運算主機上顯示的 PIN 碼,從手機連結自己的帳戶。ProviderSignInMethod
用於提供者登入,例如 Google 登入和 One Tap。QRCodeSignInMethod
:用於 QR code 登入,使用者可掃描 QR code 在手機上完成登入。適用於 Car API 級別 4 以上版本。
舉例來說,如要實作用來收集使用者密碼的範本,請先建立 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.Builder.addVariant()
方法將文字字串變體新增至 CarText
:
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();
按照偏好由高到低新增字串,例如從最長到最短。主機會根據車輛螢幕的可用空間大小,選擇適當的長度字串。
為資料列新增內嵌 CarIcon
您可以使用 CarIconSpan
新增文字內嵌圖示,讓應用程式的視覺吸引力更豐富。如要進一步瞭解如何建立這些 Span,請參閱 CarIconSpan.create
的說明文件。如需跨距文字樣式設定總覽,請參閱「使用 Span 的 Spantastic 文字樣式」。
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 開始,Car App Library 具備 API,可用於存取車輛屬性和感應器。
相關規定
如要搭配 Android Auto 使用 API,請先在 Android Auto 模組的 build.gradle
檔案中加入 androidx.car.app:app-projected
的依附元件。若是 Android Automotive OS,請將 androidx.car.app:app-automotive
的依附元件新增至 Android Automotive OS 模組的 build.gradle
檔案。
此外,您必須在 AndroidManifest.xml
檔案中宣告要求要使用的相關權限,以便要求要使用的車輛資料。請注意,使用者也必須授予這些權限。您可以在 Android Auto 和 Android Automotive OS 上使用相同程式碼,不必建立平台專屬的流程。但所需的權限並不相同。
車輛資訊
下表說明 CarInfo
API 顯示的屬性,以及使用這些 API 所須要求的權限:
方法 | 屬性 | Android Auto 權限 | Android Automotive OS 權限 |
---|---|---|---|
fetchModel |
廠牌、型號、年份 | android.car.permission.CAR_INFO |
|
fetchEnergyProfile |
電動車連接器類型、燃料類型 | com.google.android.gms.permission.CAR_FUEL |
android.car.permission.CAR_INFO |
addTollListener
removeTollListener |
付費卡狀態、付費卡類型 | ||
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 |
addSpeedListener
removeSpeedListener |
原始速度、螢幕速度 (顯示於車輛的儀表板螢幕上) | com.google.android.gms.permission.CAR_SPEED |
android.car.permission.CAR_SPEED 、android.car.permission.READ_CAR_DISPLAY_UNITS |
addMileageListener
removeMileageListener |
里程表距離 | com.google.android.gms.permission.CAR_MILEAGE |
這項資料不適用於 Android Automotive OS 和透過 Play 商店安裝的應用程式。 |
舉例來說,如要取得其餘範圍,請將 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 上測試時模擬感應器資料,請參閱 Android Automotive OS 模擬器指南的「模擬器硬體狀態」一節。
CarAppService、工作階段和畫面生命週期
Session
和 Screen
類別會實作 LifecycleOwner
介面。當使用者與應用程式互動時,系統會叫用 Session
和 Screen
物件的生命週期回呼,如下圖所示。
CarAppService 和工作階段的生命週期
詳情請參閱 Session.getLifecycle
方法的說明文件。
畫面的生命週期
詳情請參閱 Screen.getLifecycle
方法的說明文件。
透過車輛麥克風錄音
使用應用程式的 CarAppService
和 CarAudioRecord
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
可讓您模擬連至主機的連線,並確認已建立並傳回正確的 Screen
和 Template
。
如需使用範例,請參閱範例。
回報車輛專用 Android App Library 問題
如果您發現程式庫有問題,請使用 Google Issue Tracker 回報。在問題範本中,請務必填寫所有必要資訊。
提交新問題之前,請確認該問題是否列在程式庫的版本資訊或問題清單中。您可以在追蹤程式中按一下該問題的星號,訂閱該問題並投下一票。詳情請參閱訂閱問題一文。