1. Sebelum memulai
Tidak termasuk dalam cakupan
- Panduan tentang cara membuat aplikasi media (audio - misalnya musik, radio, podcast) untuk Android Auto dan Android Automotive OS. Lihat Membangun aplikasi media untuk mobil guna mengetahui detail tentang cara membangun aplikasi serupa.
Yang akan Anda butuhkan
- Pratinjau Android Studio. Beberapa emulator Android Automotive OS yang digunakan dalam codelab ini hanya tersedia melalui Pratinjau Android Studio. Jika belum menginstal Pratinjau Android Studio, Anda dapat memulai codelab dengan rilis stabil saat versi pratinjau didownload.
- Pengalaman dengan Kotlin dasar.
- Pengalaman membuat Perangkat Virtual Android dan menjalankannya di Android Emulator.
- Pengetahuan dasar tentang Jetpack Compose.
- Pemahaman tentang Efek samping
- Pemahaman dasar tentang inset jendela.
Yang akan Anda bangun
Dalam codelab ini, Anda akan mempelajari cara memigrasikan aplikasi seluler streaming video yang ada, Road Reels, ke Android Automotive OS.
Versi titik awal aplikasi yang berjalan di ponsel | Versi lengkap aplikasi yang berjalan di emulator Android Automotive OS dengan potongan layar. |
Yang akan Anda pelajari
- Cara menggunakan emulator Android Automotive OS.
- Cara membuat perubahan yang diperlukan untuk membuat build Android Automotive OS
- Asumsi umum yang dibuat ketika mengembangkan aplikasi untuk perangkat seluler yang mungkin rusak saat aplikasi berjalan di Android Automotive OS
- Berbagai tingkat kualitas untuk aplikasi di mobil
- Cara menggunakan sesi media untuk memungkinkan aplikasi lain mengontrol pemutaran aplikasi Anda
- Perbedaan UI sistem dan inset jendela di perangkat Android Automotive OS dibandingkan dengan perangkat seluler
2. Memulai persiapan
Mendapatkan kode
- Kode untuk codelab ini dapat ditemukan di direktori
build-a-parked-app
dalam repositori GitHubcar-codelabs
. Untuk membuat clone kode ini, jalankan perintah berikut:
git clone https://github.com/android/car-codelabs.git
- Atau, Anda dapat mendownload repositori sebagai file ZIP:
Membuka project
- Setelah memulai Android Studio, impor project dengan memilih direktori
build-a-parked-app/start
saja. Direktoribuild-a-parked-app/end
berisi kode solusi yang dapat Anda rujuk kapan saja jika Anda mengalami kebuntuan atau hanya ingin melihat project lengkap.
Memahami kode
- Setelah membuka project di Android Studio, luangkan waktu untuk memeriksa kode awal.
3. Mempelajari aplikasi parkir untuk Android Automotive OS
Aplikasi parkir merupakan subkumpulan kategori aplikasi yang didukung oleh Android Automotive OS. Pada saat penulisan codelab ini, aplikasi tersebut terdiri dari aplikasi streaming video, browser web, dan game. Aplikasi ini sangat cocok untuk mobil, mengingat hardware yang ada di kendaraan yang dilengkapi Google dan meningkatnya prevalensi kendaraan listrik. Waktu pengisian daya memberikan peluang besar bagi pengemudi dan penumpang untuk berinteraksi dengan jenis aplikasi ini.
Dalam banyak hal, mobil mirip dengan perangkat layar besar lainnya seperti tablet dan perangkat foldable. Mobil memiliki layar sentuh dengan ukuran, resolusi, dan rasio aspek yang serupa, dan bisa saja dalam orientasi potret atau lanskap (meskipun, tidak seperti tablet, orientasi layar mobil tetap). Mobil juga merupakan perangkat terhubung yang dapat masuk dan keluar dari koneksi jaringan. Dengan semua hal itu, tidak mengherankan jika aplikasi yang sudah dioptimalkan untuk perangkat layar besar sering kali memerlukan sedikit saja upaya untuk menghadirkan pengalaman pengguna yang sangat baik di mobil.
Serupa dengan perangkat layar besar, ada juga tingkat kualitas aplikasi untuk aplikasi di mobil:
- Tingkat 3 - Siap digunakan di mobil: Aplikasi Anda kompatibel dengan perangkat layar besar dan dapat digunakan saat mobil diparkir. Meskipun aplikasi ini mungkin tidak memiliki fitur yang dioptimalkan untuk mobil, pengguna dapat menikmati aplikasi ini seperti yang mereka lakukan di perangkat Android layar besar lainnya. Aplikasi seluler yang memenuhi persyaratan ini layak didistribusikan ke mobil secara apa adanya melalui program aplikasi seluler siap digunakan untuk mobil.
- Tingkat 2 - Dioptimalkan untuk mobil: Aplikasi Anda memberikan pengalaman yang sangat baik pada tampilan stack tengah mobil. Agar dapat digunakan di mobil, aplikasi Anda harus melalui beberapa rekayasa khusus mobil untuk menyertakan kemampuan yang dapat digunakan di seluruh mode mengemudi atau parkir, bergantung pada kategori aplikasi Anda.
- Tingkat 1 - Dirancang khusus untuk mobil: Aplikasi Anda dibuat agar berfungsi di berbagai hardware pada mobil dan dapat menyesuaikan pengalamannya di seluruh mode mengemudi dan parkir. Aplikasi ini pada tingkat ini memberikan pengalaman pengguna terbaik yang didesain untuk berbagai layar di mobil, seperti konsol tengah, cluster instrumen, dan layar tambahan - seperti tampilan panorama yang terlihat di banyak mobil premium.
4. Menjalankan aplikasi di emulator Android Automotive OS
Menginstal Image Sistem Automotive with Play Store
- Pertama, buka SDK Manager di Android Studio, lalu pilih tab SDK Platforms jika belum dipilih. Di pojok kanan bawah jendela SDK Manager, pastikan kotak Show package details dicentang.
- Instal salah satu image emulator Automotive with Play Store yang tercantum di Menambahkan Generic System Image (GSI). Image hanya dapat berjalan di komputer dengan arsitektur yang sama (x86/ARM) dengan image itu sendiri.
Membuat Perangkat Virtual Android untuk Android Automotive OS
- Setelah membuka Pengelola Perangkat, pilih Automotive di kolom Category di sisi kiri jendela. Kemudian, pilih profil hardware gabungan Automotive (1024p landscape) dari daftar, lalu klik Next.
- Di halaman berikutnya, pilih image sistem dari langkah sebelumnya. Klik Next dan pilih opsi lanjutan yang Anda inginkan sebelum akhirnya membuat AVD dengan mengklik Finish. Catatan: jika Anda memilih image API 30, image tersebut mungkin berada di tab selain tab Recommended.
Menjalankan aplikasi
Jalankan aplikasi di emulator yang baru saja Anda buat menggunakan konfigurasi run app
yang ada. Coba gunakan aplikasi di berbagai layar dan bandingkan perilakunya dengan ketika dijalankan di emulator ponsel atau tablet.
5. Membuat build Android Automotive OS
Meskipun aplikasi "berfungsi", ada beberapa perubahan kecil yang perlu dilakukan agar aplikasi berfungsi dengan baik di Android Automotive OS dan memenuhi persyaratan agar dapat dipublikasikan di Play Store. Tidak semua perubahan ini masuk akal untuk disertakan dalam aplikasi versi seluler, jadi Anda akan membuat varian build Android Automotive OS terlebih dahulu.
Menambahkan dimensi ragam faktor bentuk
Untuk memulai, tambahkan dimensi ragam untuk faktor bentuk yang ditargetkan oleh build dengan mengubah flavorDimensions
dalam file build.gradle.kts
. Kemudian, tambahkan blok productFlavors
dan ragam untuk setiap faktor bentuk (mobile
dan automotive
).
Untuk informasi selengkapnya, lihat Mengonfigurasi ragam produk.
build.gradle.kts (Module :app)
android {
...
flavorDimensions += "formFactor"
productFlavors {
create("mobile") {
// Inform Android Studio to use this flavor as the default (e.g. in the Build Variants tool window)
isDefault = true
// Since there is only one flavor dimension, this is optional
dimension = "formFactor"
}
create("automotive") {
// Since there is only one flavor dimension, this is optional
dimension = "formFactor"
// Adding a suffix makes it easier to differentiate builds (e.g. in the Play Console)
versionNameSuffix = "-automotive"
}
}
...
}
Setelah memperbarui file build.gradle.kts
, Anda akan melihat banner di bagian atas file yang memberitahukan bahwa "Gradle files have changed since the last project sync. A project sync may be necessary for the IDE to work properly". Klik tombol Sync Now di banner tersebut sehingga Android Studio dapat mengimpor perubahan konfigurasi build ini.
Selanjutnya, buka jendela alat Build Variants dari item menu Build > Select Build Variant... dan pilih varian automotiveDebug
. Tindakan ini akan memastikan Anda melihat file untuk set sumber automotive
di jendela Project dan varian build ini digunakan saat menjalankan aplikasi melalui Android Studio.
Membuat manifes Android Automotive OS
Selanjutnya, Anda akan membuat file AndroidManifest.xml
untuk set sumber automotive
. File ini berisi elemen yang diperlukan dari aplikasi Android Automotive OS.
- Di jendela Project, klik kanan modul
app
. Dari menu dropdown yang muncul, pilih New > Other > Android Manifest File - Di jendela New Android Component yang terbuka, pilih
automotive
sebagai Target Source Set untuk file baru. Klik Finish untuk membuat file.
- Dalam file
AndroidManifest.xml
yang baru saja dibuat (di jalurapp/src/automotive/AndroidManifest.xml
), tambahkan kode berikut:
AndroidManifest.xml (automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://developer.android.com/training/cars/parked#required-features -->
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true" />
<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" />
</manifest>
Deklarasi pertama diperlukan untuk mengupload artefak build ke jalur Android Automotive OS di Konsol Play. Dengan adanya fitur ini, Google Play dapat mendistribusikan aplikasi hanya ke perangkat yang memiliki fitur android.hardware.type.automotive
(yaitu mobil).
Deklarasi lainnya diperlukan untuk memastikan aplikasi dapat diinstal di berbagai konfigurasi hardware yang ada di mobil. Untuk detail selengkapnya, lihat Fitur Android Automotive OS yang diperlukan.
Menandai aplikasi sebagai aplikasi video
Bagian terakhir metadata yang perlu ditambahkan adalah file automotive_app_desc.xml
. File ini digunakan untuk mendeklarasikan kategori aplikasi Anda dalam konteks Android untuk Mobil, dan tidak bergantung pada kategori yang Anda pilih untuk aplikasi di Konsol Play.
- Klik kanan modul
app
dan pilih opsi New > Android Resource File, lalu masukkan nilai berikut sebelum mengklik OK:
- Nama file:
automotive_app_desc.xml
- Jenis resource:
XML
- Elemen root:
automotiveApp
- Set sumber:
automotive
- Nama direktori:
xml
- Dalam file tersebut, tambahkan elemen
<uses>
berikut untuk mendeklarasikan bahwa aplikasi Anda adalah aplikasi video.
automotive_app_desc.xml
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses
name="video"
tools:ignore="InvalidUsesTagAttribute" />
</automotiveApp>
- Di file
AndroidManifest.xml
set sumberautomotive
(tempat Anda baru saja menambahkan elemen<uses-feature>
), tambahkan elemen<application>
kosong. Di dalamnya, tambahkan elemen<meta-data>
berikut yang merujuk ke fileautomotive_app_desc.xml
yang baru saja Anda buat.
AndroidManifest.xml (automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<application>
<meta-data
android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc" />
</application>
</manifest>
Dengan demikian, Anda telah membuat semua perubahan yang diperlukan untuk membuat build Android Automotive OS aplikasi.
6. Memenuhi persyaratan kualitas Android Automotive OS: Kemampuan Navigasi
Meskipun membuat varian build Android Automotive OS adalah salah satu bagian dari menghadirkan aplikasi Anda di mobil, Anda tetap perlu memastikan aplikasi dapat digunakan dan aman digunakan.
Menambahkan kemampuan navigasi
Saat menjalankan aplikasi di emulator Android Automotive OS, Anda mungkin melihat bahwa kembali dari layar detail ke layar utama atau dari layar pemutar ke layar detail tidak mungkin dilakukan. Tidak seperti faktor bentuk lainnya, yang mungkin memerlukan tombol kembali atau gestur sentuh untuk mengaktifkan navigasi kembali, perangkat Android Automotive OS tidak memiliki persyaratan semacam itu. Dengan demikian, aplikasi harus menyediakan kemampuan navigasi di UI-nya untuk memastikan pengguna dapat melakukan navigasi tanpa terhenti di layar dalam aplikasi. Persyaratan ini dikodifikasi sebagai pedoman kualitas AN-1.
Untuk mendukung navigasi kembali dari layar detail ke layar utama, tambahkan parameter navigationIcon
tambahan untuk CenterAlignedTopAppBar
layar detail sebagai berikut:
RoadReelsApp.kt
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
...
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
}
Untuk mendukung navigasi kembali dari layar pemutar ke layar utama:
- Perbarui composable
TopControls
untuk mengambil parameter callback bernamaonClose
dan tambahkanIconButton
yang memanggilnya saat diklik.
PlayerControls.kt
@Composable
fun TopControls(
title: String?,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Box(modifier) {
IconButton(
modifier = Modifier
.align(Alignment.TopStart),
onClick = onClose
) {
Icon(
Icons.TwoTone.Close,
contentDescription = "Close player",
tint = Color.White
)
}
if (title != null) { ... }
}
}
- Perbarui composable
PlayerControls
untuk mengambil juga parameter callbackonClose
dan meneruskannya keTopControls
PlayerControls.kt
fun PlayerControls(
visible: Boolean,
playerState: PlayerState,
onClose: () -> Unit,
onPlayPause: () -> Unit,
onSeek: (seekToMillis: Long) -> Unit,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
Box(modifier = modifier.background(Color.Black.copy(alpha = .5f))) {
TopControls(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.screen_edge_padding))
.align(Alignment.TopCenter),
title = playerState.mediaMetadata.title?.toString(),
onClose = onClose
)
...
}
}
}
- Selanjutnya, perbarui composable
PlayerScreen
untuk mengambil parameter yang sama, dan teruskan kePlayerControls
-nya.
PlayerScreen.kt
@Composable
fun PlayerScreen(
onClose: () -> Unit,
modifier: Modifier = Modifier,
) {
...
PlayerControls(
modifier = Modifier
.fillMaxSize(),
visible = isShowingControls,
playerState = playerState,
onClose = onClose,
onPlayPause = { if (playerState.isPlaying) player.pause() else player.play() },
onSeek = { player.seekTo(it) }
)
}
- Terakhir, di
RoadReelsNavHost
, berikan implementasi yang akan diteruskan kePlayerScreen
:
RoadReelsNavHost.kt
composable(route = Screen.Player.name) {
PlayerScreen(onClose = { navController.popBackStack() })
}
Bagus, sekarang pengguna dapat berpindah antarlayar tanpa terhenti. Selain itu, pengalaman pengguna mungkin juga lebih baik pada faktor bentuk lainnya – misalnya, pada ponsel yang tinggi ketika tangan pengguna sudah berada di dekat bagian atas layar, mereka dapat dengan lebih mudah menavigasi aplikasi tanpa perlu memindahkan perangkat di tangan mereka.
Menyesuaikan dengan dukungan orientasi layar
Tidak seperti kebanyakan perangkat seluler, sebagian besar mobil memiliki orientasi tetap. Artinya, mobil tersebut mendukung orientasi lanskap atau potret, tetapi tidak keduanya, karena layarnya tidak dapat diputar. Oleh karena itu, aplikasi harus menghindari asumsi bahwa kedua orientasi didukung.
Dalam Create an Android Automotive OS manifest, Anda menambahkan dua elemen <uses-feature>
untuk fitur android.hardware.screen.portrait
dan android.hardware.screen.landscape
dengan atribut required
yang ditetapkan ke false
. Tindakan tersebut memastikan bahwa tidak ada dependensi fitur yang implisit pada salah satu orientasi layar yang dapat mencegah aplikasi didistribusikan ke mobil. Akan tetapi, elemen manifes tersebut tidak mengubah perilaku aplikasi, hanya cara pendistribusiannya.
Saat ini, aplikasi memiliki fitur berguna yang otomatis menetapkan orientasi aktivitas ke lanskap saat pemutar video terbuka, sehingga pengguna ponsel tidak perlu mengutak-atik perangkat mereka untuk mengubah orientasinya jika belum berupa lanskap.
Sayangnya, perilaku yang sama dapat menyebabkan loop berkedip atau tampilan lebar di perangkat dengan orientasi potret tetap, seperti yang dimiliki banyak mobil laik jalan saat ini.
Untuk memperbaikinya, Anda dapat menambahkan pemeriksaan berdasarkan orientasi layar yang didukung perangkat saat ini.
- Untuk menyederhanakan implementasinya, tambahkan terlebih dahulu kode berikut di
Extensions.kt
:
Extensions.kt
import android.content.Context
import android.content.pm.PackageManager
...
enum class SupportedOrientation {
Landscape,
Portrait,
}
fun Context.supportedOrientations(): List<SupportedOrientation> {
return when (Pair(
packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE),
packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
)) {
Pair(true, false) -> listOf(SupportedOrientation.Landscape)
Pair(false, true) -> listOf(SupportedOrientation.Portrait)
// For backwards compat, if neither feature is declared, both can be assumed to be supported
else -> listOf(SupportedOrientation.Landscape, SupportedOrientation.Portrait)
}
}
- Kemudian, simpan panggilan untuk menetapkan orientasi yang diminta. Karena aplikasi dapat mengalami masalah serupa dalam mode multi-aplikasi di perangkat seluler, Anda juga dapat menyertakan pemeriksaan agar tidak menyetel orientasi secara dinamis dalam kasus tersebut.
PlayerScreen.kt
import com.example.android.cars.roadreels.SupportedOrientation
import com.example.android.cars.roadreels.supportedOrientations
...
LaunchedEffect(Unit) {
...
// Only automatically set the orientation to landscape if the device supports landscape.
// On devices that are portrait only, the activity may enter a compat mode and won't get to
// use the full window available if so. The same applies if the app's window is portrait
// in multi-window mode.
if (context.supportedOrientations().contains(SupportedOrientation.Landscape)
&& !context.isInMultiWindowMode
) {
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
...
}
Layar pemutar memasuki loop berkedip pada emulator Polestar 2 sebelum menambahkan pemeriksaan (saat aktivitas tidak menangani perubahan konfigurasi | Layar pemutar memiliki tampilan lebar pada emulator Polestar 2 sebelum menambahkan pemeriksaan (saat aktivitas menangani perubahan konfigurasi | Layar pemutar tidak memiliki tampilan lebar pada emulator Polestar 2 setelah menambahkan pemeriksaan |
Karena tempat ini adalah satu-satunya lokasi di aplikasi yang menetapkan orientasi layar, aplikasi kini menghindari tampilan lebar. Di aplikasi Anda sendiri, periksa apakah ada atribut screenOrientation
atau panggilan setRequestedOrientation
yang hanya ditujukan untuk orientasi lanskap atau potret (termasuk sensor, fitur balik, dan varian pengguna), lalu hapus atau simpan jika diperlukan untuk membatasi tampilan lebar. Untuk mengetahui detail selengkapnya, lihat Mode kompatibilitas perangkat.
Menyesuaikan dengan kemampuan kontrol kolom sistem
Sayangnya, meskipun perubahan sebelumnya memastikan aplikasi tidak memasuki loop berkedip atau membuat tampilan lebar, perubahan ini juga mengekspos asumsi lain yang rusak – yaitu bahwa kolom sistem selalu dapat disembunyikan. Karena pengguna memiliki kebutuhan yang berbeda saat menggunakan mobil (dibandingkan saat menggunakan ponsel atau tablet), OEM memiliki opsi untuk mencegah aplikasi menyembunyikan kolom sistem guna memastikan bahwa kontrol kendaraan, seperti pengontrol kondisi udara, selalu dapat diakses di layar.
Akibatnya, ada potensi bagi aplikasi untuk merender di belakang kolom sistem saat aplikasi tersebut merender dalam mode imersif dan menganggap bahwa kolom tersebut dapat disembunyikan. Anda dapat melihatnya di langkah sebelumnya, karena kontrol pemutar atas dan bawah tidak lagi terlihat saat aplikasi tidak dijadikan tampilan lebar. Dalam contoh khusus ini, aplikasi tidak lagi dapat dijelajahi karena tombol untuk menutup pemutar terhalang dan fungsinya terhambat karena bilah geser tidak dapat digunakan.
Perbaikan yang termudah adalah menerapkan padding inset jendela systemBars
ke pemutar seperti berikut:
PlayerScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
...
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemBars)
) {
PlayerView(...)
PlayerControls(...)
}
Namun, solusi ini tidak ideal karena menyebabkan elemen UI berpindah-pindah ketika kolom sistem bergerak.
Untuk meningkatkan pengalaman pengguna, Anda dapat mengupdate aplikasi guna melacak inset mana yang dapat dikontrol dan menerapkan padding hanya untuk inset yang tidak dapat dikontrol.
- Karena layar lain dalam aplikasi mungkin ingin mengontrol inset jendela, sebaiknya teruskan inset yang dapat dikontrol sebagai
CompositionLocal
. Buat file baru,LocalControllableInsets.kt
, di paketcom.example.android.cars.roadreels
dan tambahkan kode berikut:
LocalControllableInsets.kt
import androidx.compose.runtime.compositionLocalOf
// Assume that no insets can be controlled by default
const val DEFAULT_CONTROLLABLE_INSETS = 0
val LocalControllableInsets = compositionLocalOf { DEFAULT_CONTROLLABLE_INSETS }
- Siapkan
OnControllableInsetsChangedListener
untuk memproses perubahan.
MainActivity.kt
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener
...
class MainActivity : ComponentActivity() {
private lateinit var onControllableInsetsChangedListener: OnControllableInsetsChangedListener
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
var controllableInsetsTypeMask by remember { mutableIntStateOf(DEFAULT_CONTROLLABLE_INSETS) }
onControllableInsetsChangedListener =
OnControllableInsetsChangedListener { _, typeMask ->
if (controllableInsetsTypeMask != typeMask) {
controllableInsetsTypeMask = typeMask
}
}
WindowCompat.getInsetsController(window, window.decorView)
.addOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
RoadReelsTheme {
RoadReelsApp(calculateWindowSizeClass(this))
}
}
}
override fun onDestroy() {
super.onDestroy()
WindowCompat.getInsetsController(window, window.decorView)
.removeOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
}
}
- Tambahkan
CompositionLocalProvider
tingkat teratas yang berisi composable tema dan aplikasi, juga yang mengikat nilai keLocalControllableInsets
.
MainActivity.kt
import androidx.compose.runtime.CompositionLocalProvider
...
CompositionLocalProvider(LocalControllableInsets provides controllableInsetsTypeMask) {
RoadReelsTheme {
RoadReelsApp(calculateWindowSizeClass(this))
}
}
- Pada pemutar, baca nilai saat ini dan gunakan untuk menentukan inset yang akan digunakan untuk padding.
PlayerScreen.kt
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.union
import androidx.compose.ui.unit.dp
import com.example.android.cars.roadreels.LocalControllableInsets
...
val controllableInsetsTypeMask = LocalControllableInsets.current
// When the system bars can be hidden, ignore them when applying padding to the player and
// controls so they don't jump around as the system bars disappear. If they can't be hidden
// include them so nothing renders behind the system bars
var windowInsetsForPadding = WindowInsets(0.dp)
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.statusBars()) == 0) {
windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.statusBars)
}
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.navigationBars()) == 0) {
windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.navigationBars)
}
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(windowInsetsForPadding)
) {
PlayerView(...)
PlayerControls(...)
}
Konten tidak berpindah-pindah ketika kolom sistem dapat disembunyikan | Konten tetap terlihat ketika kolom sistem tidak dapat disembunyikan |
Jauh lebih baik! Konten tidak akan berpindah-pindah, dan pada saat yang sama, kontrol sepenuhnya terlihat, bahkan di mobil yang kolom sistemnya tidak dapat dikontrol.
7. Memenuhi persyaratan kualitas Android Automotive OS: Gangguan bagi pengemudi
Terakhir, ada satu perbedaan utama antara mobil dan faktor bentuk lainnya, yaitu bahwa mobil digunakan untuk mengemudi. Oleh karena itu, membatasi gangguan saat berkendara sangatlah penting. Semua aplikasi parkir untuk Android Automotive OS harus menjeda pemutaran ketika mulai mengemudi. Overlay sistem muncul ketika mulai mengemudi, dan pada gilirannya, peristiwa siklus proses onPause
dipanggil untuk aplikasi yang ditempatkan. Selama panggilan ini, aplikasi harus menjeda pemutaran.
Simulasi mengemudi
Buka tampilan pemutar di emulator dan mulai memutar konten. Kemudian, ikuti langkah-langkah untuk simulasi mengemudi dan perhatikan bahwa saat UI aplikasi terhalang oleh sistem, pemutaran tidak dijeda. Hal ini melanggar pedoman kualitas aplikasi mobil DD-2.
Menjeda pemutaran ketika mulai mengemudi
- Tambahkan dependensi pada artefak
androidx.lifecycle:lifecycle-runtime-compose
, yang berisiLifecycleEventEffect
yang membantu menjalankan kode pada peristiwa siklus proses.
libs.version.toml
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
Build.gradle.kts (Module :app)
implementation(libs.androidx.lifecycle.runtime.compose)
- Setelah menyinkronkan project untuk mendownload dependensi, tambahkan
LifecycleEventEffect
yang berjalan di peristiwaON_PAUSE
untuk menjeda pemutaran.
PlayerScreen.kt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
...
@Composable
fun PlayerScreen(...) {
...
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
player.pause()
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
player.play()
}
...
}
Setelah perbaikan diterapkan, ikuti langkah yang sama seperti yang Anda lakukan sebelumnya untuk simulasi mengemudi selama pemutaran aktif, dan perhatikan bahwa pemutaran berhenti, yang memenuhi persyaratan DD-2.
8. Menguji aplikasi di emulator distant display
Konfigurasi baru yang mulai muncul di mobil adalah penyiapan dua layar dengan layar utama di konsol tengah dan layar sekunder tinggi di dasbor, dekat kaca depan. Aplikasi dapat dipindahkan dari layar tengah ke layar sekunder, lalu kembali lagi untuk memberikan lebih banyak opsi kepada pengemudi dan penumpang.
Menginstal image Automotive Distant Display
- Pertama, buka SDK Manager di Android Studio, lalu pilih tab SDK Platforms jika belum dipilih. Di pojok kanan bawah jendela SDK Manager, pastikan kotak Show package details dicentang.
- Instal image emulator Automotive Distant Display with Google API untuk arsitektur komputer Anda (x86/ARM).
Membuat Perangkat Virtual Android untuk Android Automotive OS
- Setelah membuka Pengelola Perangkat, pilih Automotive di kolom Category di sisi kiri jendela. Kemudian, pilih profil hardware paket Automotive Distant Display dari daftar, lalu klik Next.
- Di halaman berikutnya, pilih image sistem dari langkah sebelumnya. Klik Next dan pilih opsi lanjutan yang Anda inginkan sebelum akhirnya membuat AVD dengan mengklik Finish.
Menjalankan aplikasi
Jalankan aplikasi di emulator yang baru saja Anda buat menggunakan konfigurasi run app
yang ada. Ikuti petunjuk di Menggunakan emulator distant display untuk memindahkan aplikasi ke dan dari distant display. Uji pemindahan aplikasi saat berada di layar utama/detail dan saat berada di layar pemutar, serta saat mencoba berinteraksi dengan aplikasi di kedua layar.
9. Meningkatkan pengalaman aplikasi di distant display
Saat menggunakan aplikasi di distant display, Anda mungkin memperhatikan dua hal:
- Pemutaran dimulai ulang saat aplikasi dipindahkan ke dan dari distant display.
- Anda tidak dapat berinteraksi dengan aplikasi saat berada di distant display, termasuk mengubah status pemutaran.
Meningkatkan kontinuitas aplikasi
Masalah saat pemutaran dimulai ulang disebabkan oleh aktivitas yang dibuat ulang karena perubahan konfigurasi. Karena aplikasi ditulis menggunakan Compose dan konfigurasi yang berubah terkait dengan ukuran, Compose dapat menangani perubahan konfigurasi untuk Anda dengan membatasi pembuatan ulang aktivitas untuk perubahan konfigurasi berbasis ukuran. Hal ini membuat transisi antarlayar berjalan lancar, tanpa penghentian dalam pemutaran atau pemuatan ulang karena pembuatan ulang aktivitas.
AndroidManifest.xml
<activity
android:name="com.example.android.cars.roadreels.MainActivity"
...
android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout|density">
...
</activity>
Menerapkan kontrol pemutaran
Untuk memperbaiki masalah ketika aplikasi tidak dapat dikontrol saat berada di distant display, Anda dapat menerapkan MediaSession
. Sesi media menyediakan cara universal untuk berinteraksi dengan pemutar audio atau video. Untuk informasi selengkapnya, lihat Mengontrol dan memberitahukan pemutaran menggunakan MediaSession.
- Menambahkan dependensi pada artefak
androidx.media3:media3-session
libs.version.toml
androidx-media3-mediasession = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
build.gradle.kts (Module :app)
implementation(libs.androidx.media3.mediasession)
- Buat
MediaSession
menggunakan builder-nya.
PlayerScreen.kt
import androidx.media3.session.MediaSession
@Composable
fun PlayerScreen(...) {
...
val mediaSession = remember(context, player) {
MediaSession.Builder(context, player).build()
}
...
}
- Kemudian, tambahkan baris tambahan di blok
onDispose
dariDisposableEffect
di composablePlayer
untuk merilisMediaSession
saatPlayer
keluar dari hierarki komposisi.
PlayerScreen.kt
DisposableEffect(Unit) {
onDispose {
mediaSession.release()
player.release()
...
}
}
- Terakhir, saat berada di layar pemutar, Anda dapat menguji kontrol media menggunakan perintah
adb shell cmd media_session dispatch
# To play content
adb shell cmd media_session dispatch play
# To pause content
adb shell cmd media_session dispatch pause
# To toggle the playing state
adb shell cmd media_session dispatch play-pause
Dengan demikian, aplikasi ini berfungsi jauh lebih baik di mobil dengan distant display! Namun lebih dari itu, aplikasi juga berfungsi lebih baik pada faktor bentuk lainnya. Pada perangkat yang dapat memutar layar atau memungkinkan pengguna mengubah ukuran jendela aplikasi, aplikasi kini juga beradaptasi dengan lancar dalam situasi tersebut.
Selain itu, berkat integrasi sesi media, pemutaran aplikasi dapat dikontrol tidak hanya oleh kontrol hardware dan software di mobil, tetapi juga oleh sumber lain, seperti kueri Asisten Google atau tombol jeda pada perangkat headphone, sehingga pengguna memiliki lebih banyak opsi untuk mengontrol aplikasi di berbagai faktor bentuk.
10. Menguji aplikasi dengan konfigurasi sistem yang berbeda
Karena aplikasi berfungsi dengan baik di layar utama dan distant display, hal terakhir yang harus diperiksa adalah cara aplikasi menangani konfigurasi kolom sistem dan potongan layar yang berbeda. Seperti dijelaskan dalam Menggunakan inset jendela dan potongan layar, perangkat Android Automotive OS mungkin memiliki konfigurasi yang merusak asumsi yang umumnya berlaku pada faktor bentuk seluler.
Di bagian ini, Anda akan mendownload emulator yang dapat dikonfigurasi saat runtime, mengonfigurasi emulator agar memiliki kolom sistem sebelah kiri, dan menguji aplikasi dalam konfigurasi tersebut.
Menginstal image Android Automotive dengan Google API
- Pertama, buka SDK Manager di Android Studio, lalu pilih tab SDK Platforms jika belum dipilih. Di pojok kanan bawah jendela SDK Manager, pastikan kotak Show package details dicentang.
- Instal image emulator Android Automotive dengan Google API menggunakan API 33 untuk arsitektur komputer Anda (x86/ARM).
Membuat Perangkat Virtual Android untuk Android Automotive OS
- Setelah membuka Pengelola Perangkat, pilih Automotive di kolom Category di sisi kiri jendela. Kemudian, pilih profil hardware gabungan Automotive (1080p landscape) dari daftar, lalu klik Next.
- Di halaman berikutnya, pilih image sistem dari langkah sebelumnya. Klik Next dan pilih opsi lanjutan yang Anda inginkan sebelum akhirnya membuat AVD dengan mengklik Finish.
Mengonfigurasi kolom sistem samping
Seperti dijelaskan dalam Pengujian menggunakan emulator yang dapat dikonfigurasi, ada berbagai opsi untuk mengemulasi berbagai konfigurasi sistem yang ada di mobil.
Untuk tujuan codelab ini, com.android.systemui.rro.left
dapat digunakan untuk menguji berbagai konfigurasi kolom sistem. Untuk mengaktifkan kode tersebut, gunakan perintah berikut:
adb shell cmd overlay enable --user 0 com.android.systemui.rro.left
Karena aplikasi menggunakan pengubah systemBars
sebagai contentWindowInsets
dalam Scaffold
, konten sudah digambar di area yang aman di kolom sistem. Untuk melihat apa yang akan terjadi jika aplikasi mengasumsikan bahwa kolom sistem hanya muncul di bagian atas dan bawah layar, ubah parameter tersebut menjadi berikut:
RoadReelsApp.kt
contentWindowInsets = if (route?.equals(Screen.Player.name) == true) WindowInsets(0.dp) else WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
Maaf. Layar daftar dan detail dirender di belakang kolom sistem. Berkat pekerjaan sebelumnya, layar pemutar akan berfungsi dengan baik, meskipun kolom sistem tidak dapat dikontrol setelahnya.
Sebelum melanjutkan ke bagian berikutnya, pastikan untuk mengembalikan perubahan yang baru saja Anda buat pada parameter windowContentPadding
.
11. Menggunakan potongan layar
Terakhir, beberapa mobil memiliki layar dengan potongan layar yang sangat berbeda jika dibandingkan dengan yang terlihat di perangkat seluler. Sebagai ganti potongan kamera pinhole atau notch, beberapa kendaraan Android Automotive OS memiliki layar melengkung yang membuat layar menjadi non-persegi panjang.
Untuk melihat bagaimana perilaku aplikasi ketika potongan layar seperti itu ada, pertama-tama aktifkan potongan layar menggunakan perintah berikut:
adb shell cmd overlay enable --user 0 com.android.internal.display.cutout.emulation.free_form
Untuk benar-benar menguji seberapa baik perilaku aplikasi, aktifkan juga kolom sistem sebelah kiri yang digunakan di bagian terakhir, jika belum diaktifkan:
adb shell cmd overlay enable --user 0 com.android.systemui.rro.left
Sama halnya, aplikasi tidak merender ke potongan layar (bentuk persis potongan tersebut sulit diketahui saat ini, tetapi akan menjadi jelas di langkah berikutnya). Hal ini tidak menjadi masalah dan memberikan pengalaman yang lebih baik daripada aplikasi yang merender ke potongan, tetapi tidak beradaptasi dengan hati-hati.
Merender ke potongan layar
Untuk memberi pengalaman yang paling imersif kepada pengguna, Anda dapat memanfaatkan ruang layar yang lebih luas dengan merender ke potongan layar.
- Untuk merender ke potongan layar, buat file
integers.xml
guna menyimpan penggantian khusus untuk mobil. Untuk melakukannya, gunakan penentu mode UI dengan nilai Dok Mobil (nama ini adalah peninggalan dari saat hanya Android Auto yang ada, tetapi kini juga digunakan Android Automotive OS). Selain itu, karenaLAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
diperkenalkan di Android R, tambahkan juga penentu Versi Android dengan nilai 30. Lihat Menggunakan resource alternatif untuk detail selengkapnya.
- Dalam file yang baru saja Anda buat (
res/values-car-v30/integers.xml
), tambahkan kode berikut:
integers.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="windowLayoutInDisplayCutoutMode">3</integer>
</resources>
Nilai bilangan bulat 3
sesuai dengan LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
dan menggantikan nilai default 0
dari res/values/integers.xml
, yang sesuai dengan LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
. Nilai bilangan bulat ini sudah direferensikan di MainActivity.kt
untuk mengganti mode yang ditetapkan oleh enableEdgeToEdge()
. Untuk mengetahui informasi selengkapnya tentang atribut ini, lihat dokumentasi referensi.
Sekarang, saat Anda menjalankan aplikasi, perhatikan bahwa konten meluas ke potongan dan terlihat sangat imersif. Namun, panel aplikasi atas dan beberapa konten terhalang sebagian oleh potongan layar, menyebabkan masalah yang mirip dengan yang terjadi saat aplikasi menganggap kolom sistem hanya akan muncul di bagian atas dan bawah.
Memperbaiki panel aplikasi atas
Untuk memperbaiki panel aplikasi atas, Anda dapat menambahkan parameter windowInsets
berikut ke Composable CenterAlignedTopAppBar
:
RoadReelsApp.kt
import androidx.compose.foundation.layout.safeDrawing
...
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
Karena safeDrawing
terdiri dari inset displayCutout
dan systemBars
, hal ini meningkatkan parameter windowInsets
default, yang hanya menggunakan systemBars
saat memosisikan panel aplikasi atas.
Selain itu, karena panel aplikasi atas diposisikan di bagian atas jendela, Anda tidak boleh menyertakan komponen bawah inset safeDrawing
karena hal ini berpotensi menambahkan padding yang tidak perlu.
Memperbaiki layar utama
Salah satu opsi untuk memperbaiki konten di layar utama dan layar detail adalah menggunakan safeDrawing
, bukan systemBars
untuk contentWindowInsets
dari Scaffold
. Namun, aplikasi terlihat kurang imersif menggunakan opsi tersebut, dengan konten yang tiba-tiba terpotong saat potongan layar dimulai – tidak lebih baik dibandingkan jika aplikasi tidak dirender ke potongan layar sama sekali.
Untuk antarmuka pengguna yang lebih imersif, Anda dapat menangani inset pada setiap komponen dalam layar.
- Perbarui
contentWindowInsets
dariScaffold
agar selalu menjadi 0 dp (bukan hanya untukPlayerScreen
). Hal ini memungkinkan setiap layar dan/atau komponen dalam layar menentukan perilakunya terkait inset.
RoadReelsApp.kt
Scaffold(
...,
contentWindowInsets = WindowInsets(0.dp)
) { ... }
- Setel
windowInsetsPadding
dari composableText
header baris untuk menggunakan komponen horizontal insetsafeDrawing
. Komponen atas inset ini ditangani oleh panel aplikasi atas, dan komponen bawah akan ditangani nanti.
MainScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
...
LazyColumn(
contentPadding = PaddingValues(bottom = dimensionResource(R.dimen.screen_edge_padding))
) {
items(NUM_ROWS) { rowIndex: Int ->
Text(
"Row $rowIndex",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.padding(
horizontal = dimensionResource(R.dimen.screen_edge_padding),
vertical = dimensionResource(R.dimen.row_header_vertical_padding)
)
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))
)
...
}
- Hapus parameter
contentPadding
dariLazyRow
. Kemudian, di awal dan akhir setiapLazyRow
, tambahkanSpacer
selebar komponensafeDrawing
yang sesuai untuk memastikan semua thumbnail dapat dilihat sepenuhnya. Gunakan pengubahwidthIn
untuk memastikan pengatur jarak ini setidaknya selebar padding konten. Tanpa elemen ini, item di awal dan akhir baris mungkin akan terhalang di belakang kolom sistem dan/atau potongan layar, bahkan saat digeser sepenuhnya ke awal/akhir baris.
MainScreen.kt
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsEndWidth
import androidx.compose.foundation.layout.windowInsetsStartWidth
...
LazyRow(
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.list_item_spacing)),
) {
item {
Spacer(
Modifier
.windowInsetsStartWidth(WindowInsets.safeDrawing)
.widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
)
}
items(NUM_ITEMS_PER_ROW) { ... }
item {
Spacer(
Modifier
.windowInsetsEndWidth(WindowInsets.safeDrawing)
.widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
)
}
}
- Terakhir, tambahkan
Spacer
di akhirLazyColumn
untuk memperhitungkan setiap kolom sistem atau inset potongan layar yang potensial di bagian bawah layar. Pengatur jarak yang setara di bagian atasLazyColumn
tidak diperlukan karena panel aplikasi atas akan menanganinya. Jika aplikasi menggunakan panel aplikasi bawah, bukan panel aplikasi atas, Anda perlu menambahkanSpacer
di awal daftar menggunakan pengubahwindowInsetsTopHeight
. Dan jika aplikasi menggunakan panel aplikasi atas dan bawah, tidak ada pengatur jarak yang diperlukan.
MainScreen.kt
import androidx.compose.foundation.layout.windowInsetsBottomHeight
...
LazyColumn(...){
items(NUM_ROWS) { ... }
item {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
}
Sempurna, panel aplikasi atas sepenuhnya terlihat dan, saat men-scroll ke akhir baris, Anda kini dapat melihat semua thumbnail secara keseluruhan.
Memperbaiki layar detail
Layar detail tidak terlalu buruk, tetapi konten masih terpotong.
Karena layar detail tidak memiliki konten yang dapat di-scroll, yang diperlukan untuk memperbaikinya adalah menambahkan pengubah windowInsetsPadding
pada Box
tingkat atas.
DetailScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
...
Box(
modifier = modifier
.padding(dimensionResource(R.dimen.screen_edge_padding))
.windowInsetsPadding(WindowInsets.safeDrawing)
) { ... }
Memperbaiki layar pemutar
Meskipun PlayerScreen
sudah menerapkan padding untuk beberapa atau semua inset jendela kolom sistem di bagian Memenuhi persyaratan kualitas Android Automotive OS: Kemampuan Navigasi, hal itu tidak cukup untuk memastikan bahwa sekarang layar pemutar tidak tertutup sehingga aplikasi dirender ke potongan layar. Pada perangkat seluler, potongan layar hampir selalu sepenuhnya berada dalam kolom sistem. Namun, di mobil, potongan layar mungkin jauh melampaui kolom sistem, sehingga melanggar asumsi.
Untuk memperbaikinya, cukup ubah nilai awal variabel windowInsetsForPadding
dari nilai nol menjadi displayCutout
:
PlayerScreen.kt
import androidx.compose.foundation.layout.displayCutout
...
var windowInsetsForPadding = WindowInsets(WindowInsets.displayCutout)
Bagus, aplikasi ini benar-benar mengoptimalkan layar sekaligus tetap dapat digunakan.
Selain itu, jika Anda menjalankan aplikasi di perangkat seluler, aplikasi tersebut juga akan lebih imersif. Item daftar dirender hingga ke tepi layar, termasuk di belakang menu navigasi.
12. Selamat
Anda berhasil memigrasikan dan mengoptimalkan aplikasi parkir pertama Anda. Sekarang saatnya menggunakan yang telah Anda pelajari dan menerapkannya ke aplikasi Anda sendiri.
Untuk dicoba
- Ganti beberapa nilai resource dimensi untuk menambah ukuran elemen saat dijalankan di mobil
- Coba lebih banyak konfigurasi emulator yang dapat dikonfigurasi
- Uji aplikasi menggunakan beberapa image emulator OEM yang tersedia
Bacaan lebih lanjut
- Membangun aplikasi parkir untuk Android Automotive OS
- Membangun aplikasi video untuk Android Automotive OS
- Membangun game untuk Android Automotive OS
- Membangun browser untuk Android Automotive OS
- Halaman Kualitas aplikasi Android untuk mobil menjelaskan kriteria yang harus dipenuhi aplikasi Anda untuk menciptakan pengalaman pengguna yang sangat baik dan lulus peninjauan Play Store. Pastikan Anda memfilter kategori aplikasi.