Android for Cars 應用程式庫可讓您將導航、搜尋點 (POI)、物聯網 (IoT) 或天氣應用程式帶到車上。這個程式庫提供一組範本,可滿足駕駛人分心標準,並處理各種車輛螢幕因素和輸入模式等細節。
本指南會概略說明程式庫的主要功能和概念,並逐步引導您設定基本應用程式。
事前準備
- 詳閱「為駕駛人設計」頁面,瞭解車輛專用應用程式程式庫
- 導航應用程式 和其他駕駛相關應用程式 類別總覽
- 使用範本建構應用程式總覽
- 基礎架構 涵蓋範本和範本元件
- 範例流程 展示常見的 UX 模式
- 範本應用程式規定
- 請參閱下節的重要詞彙與概念。
- 熟悉 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
:使用者在車內即可下達指令,針對已連結的裝置進行相關操作。請參閱「建構車輛專用物聯網應用程式」。androidx.car.app.category.WEATHER
:這類應用程式可讓使用者查看目前位置或路線沿途的相關天氣資訊。請參閱「打造車用天氣應用程式」。androidx.car.app.category.MEDIA
:這類應用程式可讓使用者在車上瀏覽及播放音樂、電台、有聲書和其他音訊內容。請參閱「打造車用範本媒體應用程式」。androidx.car.app.category.MESSAGING
:可讓使用者透過簡訊通訊的應用程式。請參閱「建構 Android Auto 的範本訊息體驗」。androidx.car.app.category.CALLING
:可讓使用者透過語音通話通訊的應用程式。請參閱「建構 Android Auto 的通話體驗」。
如要詳細瞭解各類別,以及應用程式所屬類別的條件,請參閱「車用 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 級別
車輛應用程式程式庫會定義自己的 API 級別,方便您瞭解車輛上的範本主機支援哪些程式庫功能。如要擷取主機支援的最高 Car App API 級別,請使用 getCarAppApiLevel()
方法。
在 AndroidManifest.xml
檔案中,宣告應用程式支援的最低 Car App API 級別:
<manifest ...>
<application ...>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1"/>
</application>
</manifest>
如要瞭解如何維持回溯相容性,以及宣告使用某項功能所需的最低 API 級別,請參閱 RequiresCarApi
註解的說明文件。如要瞭解使用 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
方法,該方法會傳回代表要在車輛螢幕上顯示的 UI 狀態的 Template
執行個體。
下列程式碼片段說明如何宣告 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
物件是標準的 Action
,會自動叫用 ScreenManager.pop
。您可以使用 CarContext
提供的 OnBackPressedDispatcher
例項,覆寫這項行為。
為確保應用程式在行車時安全無虞,螢幕堆疊最多只能有五個螢幕。詳情請參閱「範本限制」一節。
重新整理範本內容
應用程式可以呼叫 Screen.invalidate
方法,要求使 Screen
的內容失效。主機隨後會呼叫應用程式的 Screen.onGetTemplate
方法,以使用新內容擷取範本。
重新整理 Screen
時,請務必瞭解範本中可更新的特定內容,以免主機將新範本計入範本配額。詳情請參閱「範本限制」一節。
建議您架構畫面,讓 Screen
與透過 onGetTemplate
實作傳回的範本類型之間,存在一對一的對應關係。
繪製地圖
使用下列範本的導航、搜尋點 (POI) 和天氣應用程式可以存取 Surface
來繪製地圖。
如要使用下列範本,應用程式必須在 AndroidManifest.xml
檔案的 <uses-permission>
元素中,宣告其中一個對應權限。
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 |
導覽 |
宣告 Surface 權限
除了應用程式使用的範本所需權限外,應用程式也必須在 AndroidManifest.xml
檔案中宣告 androidx.car.app.ACCESS_SURFACE
權限,才能存取介面:
<manifest ...>
...
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
...
</manifest>
存取平台
如要存取主機提供的 Surface
,您必須實作 SurfaceCallback
,並將該實作提供給 AppManager
車輛服務。目前的 Surface
會在 onSurfaceAvailable()
和 onSurfaceDestroyed()
回呼的 SurfaceContainer
參數中,傳遞至 SurfaceCallback
。
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 (introduction of template) |
MapWithContentTemplate |
7 (範本簡介) |
實作互動式回呼
SurfaceCallback
介面提供多種回呼方法,可供您實作,為使用前一節範本建構的地圖新增互動功能:
互動 | SurfaceCallback 方法 |
開始支援的 Car App API 級別 |
---|---|---|
輕觸 | onClick |
5 |
雙指撥動縮放 | onScale |
2 |
單點觸控拖曳 | onScroll |
2 |
單點觸控滑動 | onFling |
2 |
輕觸兩下 | onScale (由範本主機決定縮放比例係數) |
2 |
平移模式的旋轉自動提醒 | onScroll (由範本主機決定距離係數) |
2 |
新增地圖動作區域
這些範本可包含地圖動作列,用來顯示地圖相關動作,例如縮放、重新置中、顯示指南針,以及其他選擇顯示的動作。Google 地圖動作列最多可有四個純圖示按鈕,這些按鈕可重新整理,並且不影響工作深度。處於閒置狀態時,這個動作列會隱藏,待回到啟用狀態才會再顯示。
如要接收地圖互動回呼,您必須在 Google 地圖動作列中新增 Action.PAN
按鈕。使用者按下平移按鈕時,主機會進入平移模式,如下一節所述。
如果應用程式忽略地圖動作列中的 Action.PAN
的按鈕,就無法從 SurfaceCallback
方法收到使用者輸入內容,而且主機會結束先前啟用的平移模式。
如果是觸控螢幕,畫面上不會顯示平移按鈕。
瞭解平移模式
在平移模式下,使用者透過非觸控輸入裝置 (例如旋轉控制器和觸控板) 輸入的內容,會由範本主機轉譯成適當的 SurfaceCallback
方法。使用 NavigationTemplate.Builder
中的 setPanModeListener
方法,可在使用者進入或結束平移模式時做出反應。使用者處於平移模式時,主機可隱藏範本中的其他 UI 元件。
與使用者互動
應用程式可使用類似行動應用程式的模式與使用者互動。
處理使用者輸入內容
應用程式可以將適當的事件監聽器傳遞至支援這些監聽器的模型,藉此回應使用者輸入內容。以下程式碼片段說明如何建立 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
Intent 的格式),請參閱「使用 Intent 啟動車輛應用程式」一節。
部分動作 (例如將使用者導向行動裝置,繼續互動) 只能在車輛停妥時執行。您可以使用 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
,則高優先順序通知只會顯示一次抬頭通知。
如要進一步瞭解如何設計車輛應用程式的通知,請參閱 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()
方法。
相較於使用標準 Android API,使用 CarContext.requestPermissions()
的優點是不必啟動自己的 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
時,系統會叫用該 BroadcastReceiver
來處理意圖,包括資料 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())); } } }
如要進一步瞭解如何處理車用應用程式的通知,請參閱「顯示通知」一節。
範本限制
主辦人可將特定工作顯示的範本數量限制為最多五個,其中最後一個範本必須是下列其中一種:
NavigationTemplate
PaneTemplate
MessageTemplate
MediaPlaybackTemplate
SignInTemplate
LongMessageTemplate
請注意,這項限制適用於範本數量,而非堆疊中的 Screen
執行個體數量。舉例來說,如果應用程式在畫面 A 中傳送兩個範本,然後推送畫面 B,現在可以再傳送三個範本。或者,如果每個畫面都設定為傳送單一範本,應用程式就能將五個畫面例項推送至 ScreenManager
堆疊。
但範本重新整理、返回和重設作業不受這些限制。
範本重新整理
部分內容更新不會計入範本上限。一般來說,如果應用程式推送的新範本類型相同,且包含與先前範本相同的主要內容,新範本就不會計入配額。舉例來說,更新 ListTemplate
中某列的切換按鈕狀態,不會計入配額。如要進一步瞭解哪些類型的內容更新可視為重新整理,請參閱個別範本的說明文件。
後端作業
如要在工作內啟用子流程,主機會偵測應用程式何時從 ScreenManager
堆疊彈出 Screen
,並根據應用程式返回的範本數量,更新剩餘配額。
舉例來說,如果應用程式在畫面 A 中傳送兩個範本,然後推送畫面 B 並再傳送兩個範本,則應用程式還剩一個配額。如果應用程式隨後返回畫面 A,主機會將配額重設為三,因為應用程式已回溯兩個範本。
請注意,應用程式返回畫面時,必須傳送與該畫面上次傳送的範本類型相同的範本。傳送其他範本類型會導致錯誤。不過,只要在返回作業期間類型保持不變,應用程式就能自由修改範本內容,不會影響配額。
重設作業
某些範本具有特殊語意,表示工作已完成。舉例來說,NavigationTemplate
檢視區塊應會持續顯示在畫面上,並為使用者提供新的路線導航指示。當主機達到其中一個範本時,會重設範本配額,並將該範本視為新工作的第一步。應用程式可藉此開始執行新工作。
請參閱個別範本的說明文件,瞭解哪些範本會觸發主機重設。
如果主機收到從通知動作或啟動器啟動應用程式的意圖,配額也會重設。這個機制可讓應用程式從通知開始新的工作流程,即使應用程式已繫結並處於前景,也適用這項機制。
如要進一步瞭解如何在車輛螢幕上顯示應用程式通知,請參閱「顯示通知」一節。如要瞭解如何透過通知動作啟動應用程式,請參閱「透過意圖啟動車用應用程式」一節。
Connection 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(); }
Constraints 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
。車輛應用程式程式庫目前支援下列登入方式:
InputSignInMethod
使用者名稱/密碼登入。PinSignInMethod
透過 PIN 碼登入,使用者可從手機連結帳戶, 並使用車載主機上顯示的 PIN 碼。ProviderSignInMethod
適用於供應商登入,例如 Google 登入 和 One Tap。QRCodeSignInMethod
QR code 登入,使用者掃描 QR code 即可在手機上完成登入。這項功能適用於 Car API Level 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
提升應用程式的視覺吸引力。如要進一步瞭解如何建立這些範圍,請參閱 CarIconSpan.create
說明文件。如要瞭解如何使用 Span 設定文字樣式,請參閱「使用 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,請在 Android Automotive OS 模組的 build.gradle
檔案中新增 androidx.car.app:app-automotive
的依附元件。
此外,您還需要在 AndroidManifest.xml
檔案中聲明相關權限,才能要求使用所需的車輛資料。請注意,使用者也必須授予您這些權限。您可以在 Android Auto 和 Android Automotive OS 上使用相同程式碼,不必建立平台專屬流程。但所需權限不同。
CarInfo
下表說明 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
這項資料僅適用於執行 API 30 以上版本的特定 Android Automotive OS 車輛。 |
外部尺寸 | 無 | 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 |
如果是從 Play 商店安裝的應用程式,則無法在 Android Automotive OS 上取得這項資料。 | 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
CarSensors
類別可讓您存取車輛的加速計、陀螺儀、指南針和位置資料。這些值是否可用可能取決於原始設備製造商。加速計、陀螺儀和指南針的資料格式與 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 生命週期
Session
和 Screen
類別會實作 LifecycleOwner
介面。使用者與應用程式互動時,系統會叫用 Session
和 Screen
物件的生命週期回呼,如下圖所示。
CarAppService 和 Session 的生命週期

Session
生命週期。如要查看完整詳細資料,請參閱 Session.getLifecycle
方法的說明文件。
畫面生命週期

Screen
生命週期。如要查看完整詳細資料,請參閱 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 for Cars 測試程式庫提供輔助類別,可用於驗證測試環境中的應用程式行為。舉例來說,
SessionController
可模擬與主機的連線,並驗證是否已建立及傳回正確的
Screen
和
Template
。
如需使用範例,請參閱「範例」。
回報車輛專用 Android App Library 相關問題
如果您發現程式庫有問題,請使用 Google Issue Tracker 回報。在問題範本中,請務必填寫所有必要資訊。
提交新問題之前,請先查看程式庫的版本資訊中是否列出了該問題,或是在問題清單中列出了該問題。您可以在追蹤程式中按一下該問題的星號,訂閱該問題並投下一票。詳情請參閱訂閱問題一文。