1. บทนำ
ระบบนิเวศของอุปกรณ์ Android มีการเปลี่ยนแปลงอยู่เสมอ ตั้งแต่ยุคแรกๆ ของแป้นพิมพ์ที่เป็นฮาร์ดแวร์ในตัวไปจนถึงลักษณะการใช้งานแบบพับได้ อุปกรณ์แบบพับได้ แท็บเล็ต และหน้าต่างที่ปรับขนาดได้แบบอิสระ แอป Android ไม่เคยทำงานกับชุดอุปกรณ์ที่หลากหลายมากกว่าในปัจจุบัน
แม้ว่านี่จะเป็นข่าวดีสำหรับนักพัฒนาซอฟต์แวร์ แต่การเพิ่มประสิทธิภาพแอปบางอย่างก็จำเป็นต้องเพื่อให้เป็นไปตามความคาดหวังด้านความสามารถในการใช้งานและเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ยอดเยี่ยมในหน้าจอขนาดต่างๆ แทนที่จะกำหนดเป้าหมายเป็นอุปกรณ์ใหม่ทีละเครื่อง การใช้ UI ที่ตอบสนอง/ปรับเปลี่ยนได้และสถาปัตยกรรมที่ยืดหยุ่นจะช่วยให้แอปของคุณดูดีและทำงานได้ดีในทุกที่ทั้งผู้ใช้ปัจจุบันและในอนาคต บนอุปกรณ์ทุกขนาดและรูปร่าง
การเปิดตัวสภาพแวดล้อม Android ที่ปรับขนาดได้รูปแบบอิสระเป็นวิธีที่ยอดเยี่ยมในการทดสอบ UI ที่ตอบสนอง/ปรับเปลี่ยนได้ของคุณ เพื่อเตรียมพร้อมสำหรับทุกๆ อุปกรณ์ Code Lab นี้จะช่วยคุณทำความเข้าใจผลของการปรับขนาด รวมถึงนำแนวทางปฏิบัติแนะนำบางอย่างไปใช้เพื่อการปรับขนาดแอปให้มีประสิทธิภาพและง่ายดาย
สิ่งที่คุณจะสร้าง
คุณจะได้สำรวจผลกระทบของการปรับขนาดรูปแบบอิสระและเพิ่มประสิทธิภาพแอป Android เพื่อแสดงแนวทางปฏิบัติแนะนำในการปรับขนาด แอปของคุณจะ
มีไฟล์ Manifest ที่เข้ากันได้
- ลบข้อจำกัดที่ป้องกันไม่ให้แอปปรับขนาดได้อย่างอิสระ
คงสถานะไว้เมื่อปรับขนาด
- คงสถานะ UI เมื่อปรับขนาดโดยใช้ rememberSaveable
- หลีกเลี่ยงการทำซ้ำพื้นหลังโดยไม่จำเป็นเพื่อเริ่มต้น UI
สิ่งที่ต้องมี
- ความรู้ในการสร้างแอปพลิเคชัน Android พื้นฐาน
- ความรู้เกี่ยวกับ ViewModel และ State in Compose
- อุปกรณ์ทดสอบที่รองรับการปรับขนาดหน้าต่างรูปแบบอิสระ เช่น สิ่งใดสิ่งหนึ่งต่อไปนี้
- Chromebook ที่ตั้งค่า ADB
- แท็บเล็ตที่รองรับ Samsung DeX Mode หรือโหมดเพิ่มประสิทธิภาพ
- โปรแกรมจำลองอุปกรณ์เสมือน Android บนเดสก์ท็อปใน Android Studio
หากพบปัญหา (ข้อบกพร่องของโค้ด ข้อผิดพลาดด้านไวยากรณ์ การใช้คำที่ไม่ชัดเจน ฯลฯ) ขณะดำเนินการใน Codelab โปรดรายงานปัญหาผ่านลิงก์รายงานข้อผิดพลาดที่มุมล่างซ้ายของ Codelab
2. เริ่มต้นใช้งาน
โคลนที่เก็บจาก GitHub
git clone https://github.com/android/large-screen-codelabs/
...หรือดาวน์โหลดไฟล์ ZIP ของที่เก็บและแตกไฟล์
นำเข้าโปรเจ็กต์
- เปิด Android Studio
- เลือกนำเข้าโปรเจ็กต์ หรือไฟล์->ใหม่->นำเข้าโปรเจ็กต์
- นำทางไปยังตำแหน่งที่คุณโคลนหรือแตกข้อมูลโปรเจ็กต์
- เปิดโฟลเดอร์การปรับขนาด
- เปิดโปรเจ็กต์ในโฟลเดอร์ start ซึ่งจะมีโค้ดเริ่มต้น
ลองใช้แอป
- สร้างและเรียกใช้แอป
- ลองปรับขนาดแอป
คุณมีความคิดเห็นอย่างไร
คุณอาจสังเกตเห็นว่าประสบการณ์ของผู้ใช้ไม่ดีเท่าที่ควร ทั้งนี้ขึ้นอยู่กับการรองรับความเข้ากันได้ของอุปกรณ์ทดสอบ ปรับขนาดแอปไม่ได้และค้างอยู่ที่สัดส่วนภาพเริ่มต้น จะเกิดอะไรขึ้น
ข้อจำกัดของไฟล์ Manifest
หากคุณดูในไฟล์ AndroidManifest.xml
ของแอป คุณจะเห็นว่ามีข้อจำกัดบางอย่างที่ทำให้แอปทำงานได้ไม่ดีในสภาพแวดล้อมการปรับขนาดหน้าต่างรูปแบบอิสระ
AndroidManifest.xml
android:maxAspectRatio="1.4"
android:resizeableActivity="false"
android:screenOrientation="portrait">
ลองนำบรรทัดที่เป็นปัญหา 3 บรรทัดนี้ออกจากไฟล์ Manifest สร้างแอปใหม่ แล้วลองอีกครั้งในอุปกรณ์ทดสอบ คุณจะเห็นว่าแอปไม่ได้ถูกจำกัดจากการปรับขนาดรูปแบบอิสระอีกต่อไป การนำข้อจำกัดลักษณะนี้ออกจากไฟล์ Manifest เป็นขั้นตอนสำคัญในการเพิ่มประสิทธิภาพแอปสำหรับการปรับขนาดหน้าต่างรูปแบบอิสระ
3. การเปลี่ยนแปลงการกำหนดค่าของการปรับขนาด
เมื่อปรับขนาดหน้าต่างของแอปแล้ว ระบบจะอัปเดตการกำหนดค่าของแอป การอัปเดตเหล่านี้มีผลกระทบต่อแอปของคุณ การทำความเข้าใจและคาดหวังว่าผู้ใช้จะสามารถมอบประสบการณ์การใช้งานที่ยอดเยี่ยมแก่ผู้ใช้ การเปลี่ยนแปลงที่เห็นได้ชัดที่สุดคือความกว้างและความสูงของหน้าต่างแอป แต่การเปลี่ยนแปลงเหล่านี้จะมีผลต่อสัดส่วนภาพและการวางแนวด้วย
กําลังสังเกตการเปลี่ยนแปลงการกําหนดค่า
หากต้องการดูการเปลี่ยนแปลงเหล่านี้ที่เกิดขึ้นเองในแอปที่สร้างด้วยระบบมุมมองของ Android คุณลบล้าง View.onConfigurationChanged
ได้ ใน Jetpack Compose เรามีสิทธิ์เข้าถึง LocalConfiguration.current
ซึ่งจะอัปเดตโดยอัตโนมัติทุกครั้งที่มีการเรียกใช้ View.onConfigurationChanged
หากต้องการดูการเปลี่ยนแปลงการกำหนดค่าเหล่านี้ในแอปตัวอย่าง ให้เพิ่ม Composable ในแอปที่แสดงค่าจาก LocalConfiguration.current
หรือสร้างโปรเจ็กต์ตัวอย่างใหม่ด้วย Composable ดังกล่าว ตัวอย่าง UI ที่จะเห็นข้อความเหล่านี้
val configuration = LocalConfiguration.current
val isPortrait = configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
val screenLayoutSize =
when (configuration.screenLayout and
Configuration.SCREENLAYOUT_SIZE_MASK) {
SCREENLAYOUT_SIZE_SMALL -> "SCREENLAYOUT_SIZE_SMALL"
SCREENLAYOUT_SIZE_NORMAL -> "SCREENLAYOUT_SIZE_NORMAL"
SCREENLAYOUT_SIZE_LARGE -> "SCREENLAYOUT_SIZE_LARGE"
SCREENLAYOUT_SIZE_XLARGE -> "SCREENLAYOUT_SIZE_XLARGE"
else -> "undefined value"
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("screenWidthDp: ${configuration.screenWidthDp}")
Text("screenHeightDp: ${configuration.screenHeightDp}")
Text("smallestScreenWidthDp: ${configuration.smallestScreenWidthDp}")
Text("orientation: ${if (isPortrait) "portrait" else "landscape"}")
Text("screenLayout SIZE: $screenLayoutSize")
}
ดูตัวอย่างการใช้งานได้ในโฟลเดอร์โปรเจ็กต์ observing-configuration-changes ลองเพิ่มข้อมูลนี้ลงใน UI ของแอป เรียกใช้ในอุปกรณ์ทดสอบ และดูการอัปเดต UI เมื่อการกำหนดค่าของแอปมีการเปลี่ยนแปลง
การเปลี่ยนแปลงการกำหนดค่าแอปเหล่านี้ช่วยให้คุณสามารถจำลองการย้ายจากจุดที่ไกลสุดขั้วได้อย่างรวดเร็วด้วยการแยกหน้าจอในโทรศัพท์มือถือขนาดเล็กไปเป็นแบบเต็มหน้าจอบนแท็บเล็ตหรือเดสก์ท็อป วิธีนี้นอกจากจะเป็นวิธีที่ดีในการทดสอบเลย์เอาต์ของแอปในหน้าจอต่างๆ แล้ว ยังช่วยให้คุณทดสอบได้ว่าแอปรับมือกับเหตุการณ์การเปลี่ยนแปลงการกำหนดค่าอย่างรวดเร็วได้ดีเพียงใดอีกด้วย
4. การบันทึกเหตุการณ์ในวงจรของกิจกรรม
อีกนัยหนึ่งของการปรับขนาดหน้าต่างรูปแบบอิสระสําหรับแอปคือการเปลี่ยนแปลงActivity
วงจรต่างๆ ที่จะเกิดขึ้นกับแอป หากต้องการดูการเปลี่ยนแปลงเหล่านี้แบบเรียลไทม์ ให้เพิ่มผู้สังเกตการณ์วงจรลงในเมธอด onCreate
แล้วบันทึกเหตุการณ์วงจรใหม่แต่ละเหตุการณ์โดยการลบล้าง onStateChanged
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d("resizing-codelab-lifecycle", "$event was called")
}
})
เมื่อนำการบันทึกนี้ไปใช้แล้ว ให้เรียกใช้แอปในอุปกรณ์ทดสอบอีกครั้ง แล้วดูที่ logcat ขณะที่พยายามย่อแอปและนำแอปไปไว้ในเบื้องหน้าอีกครั้ง
สังเกตว่าแอปหยุดชั่วคราวเมื่อย่อขนาดแล้ว จากนั้นกลับมาทำงานอีกครั้งเมื่อเข้าสู่เบื้องหน้า ข้อมูลนี้จะมีผลต่อแอปซึ่งคุณจะสำรวจในส่วนที่กำลังจะเผยแพร่ของ Codelab นี้ซึ่งมุ่งเน้นไปที่ความต่อเนื่อง
ตอนนี้ดูที่ Logcat เพื่อดูว่ามีการเรียก Callback ของวงจรกิจกรรมใดเมื่อคุณปรับขนาดแอปจากขนาดที่เล็กที่สุดให้ใหญ่ที่สุด
คุณอาจสังเกตเห็นลักษณะการทำงานที่แตกต่างกัน ทั้งนี้ขึ้นอยู่กับอุปกรณ์ทดสอบ แต่คุณอาจสังเกตเห็นว่ากิจกรรมถูกทำลายและสร้างขึ้นใหม่เมื่อขนาดหน้าต่างของแอปมีการเปลี่ยนแปลงอย่างมาก แต่ไม่ใช่เมื่อมีการเปลี่ยนแปลงเล็กน้อย นั่นเป็นเพราะว่าใน API 24 ขึ้นไป เฉพาะการเปลี่ยนแปลงขนาดที่มีนัยสำคัญเท่านั้นที่จะส่งผลให้เกิดการสร้าง Activity
ขึ้นมาใหม่
คุณได้เห็นการเปลี่ยนแปลงการกำหนดค่าทั่วไปที่คุณอาจพบในสภาพแวดล้อมกรอบเวลารูปแบบอิสระ แต่มีการเปลี่ยนแปลงอื่นๆ ที่ควรทราบ ตัวอย่างเช่น หากคุณมีจอภาพภายนอกที่เชื่อมต่อกับอุปกรณ์ทดสอบ คุณจะเห็นว่า Activity
ถูกทำลายและสร้างขึ้นใหม่เพื่อรองรับการเปลี่ยนแปลงการกำหนดค่า เช่น ความหนาแน่นของการแสดงผล
หากต้องการสรุปความซับซ้อนบางส่วนที่เกี่ยวข้องกับการเปลี่ยนแปลงการกำหนดค่า ให้ใช้ API ระดับที่สูงกว่า เช่น WindowSizeClass เพื่อใช้งาน UI แบบปรับอัตโนมัติ (โปรดดูหัวข้อรองรับหน้าจอขนาดต่างๆ)
5. Continuity - การดูแลรักษา Composables สถานะภายในเมื่อปรับขนาด
ในส่วนก่อนหน้านี้ คุณได้เห็นการเปลี่ยนแปลงการกำหนดค่าบางอย่างที่แอปจะเกิดขึ้นในสภาพแวดล้อมการปรับขนาดหน้าต่างรูปแบบอิสระ ในส่วนนี้ คุณจะต้องรักษาสถานะ UI ของแอปให้ต่อเนื่องตลอดการเปลี่ยนแปลงเหล่านี้
เริ่มต้นด้วยการทำให้ฟังก์ชัน Composable NavigationDrawerHeader
(พบได้ใน ReplyHomeScreen.kt
) ขยายเพื่อแสดงที่อยู่อีเมลเมื่อคลิก
@Composable
private fun NavigationDrawerHeader(
modifier: Modifier = Modifier
) {
var showDetails by remember { mutableStateOf(false) }
Column(
modifier = modifier.clickable {
showDetails = !showDetails
}
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ReplyLogo(
modifier = Modifier
.size(dimensionResource(R.dimen.reply_logo_size))
)
ReplyProfileImage(
drawableResource = LocalAccountsDataProvider
.userAccount.avatar,
description = stringResource(id = R.string.profile),
modifier = Modifier
.size(dimensionResource(R.dimen.profile_image_size))
)
}
AnimatedVisibility (showDetails) {
Text(
text = stringResource(id = LocalAccountsDataProvider
.userAccount.email),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.padding(
start = dimensionResource(
R.dimen.drawer_padding_header),
end = dimensionResource(
R.dimen.drawer_padding_header),
bottom = dimensionResource(
R.dimen.drawer_padding_header)
),
)
}
}
}
เมื่อเพิ่มส่วนหัวที่ขยายได้ลงในแอปแล้ว
- เรียกใช้แอปในอุปกรณ์ทดสอบ
- แตะส่วนหัวเพื่อขยาย
- ลองปรับขนาดหน้าต่าง
คุณจะเห็นว่าส่วนหัวสูญเสียสถานะไปเมื่อมีการปรับขนาดอย่างมาก
สถานะ UI หายไปเนื่องจาก remember
ช่วยให้คุณรักษาสถานะในการจัดองค์ประกอบใหม่ แต่ไม่ใช้กับกิจกรรมหรือกระบวนการสร้างนิสัยใหม่ เป็นเรื่องปกติที่จะใช้การรอกสถานะ โดยการย้ายสถานะไปยัง Call ของ Composable เพื่อทำให้ Composable ไม่เก็บสถานะ ซึ่งจะหลีกเลี่ยงปัญหานี้ได้ทั้งหมด อย่างไรก็ตาม คุณอาจใช้ remember
ในที่ต่างๆ เมื่อคงสถานะองค์ประกอบ UI ไว้ภายในกับฟังก์ชันที่ประกอบกันได้
ในการแก้ไขปัญหาเหล่านี้ ให้แทนที่ remember
ด้วย rememberSaveable
ที่เป็นเช่นนี้เพราะ rememberSaveable
บันทึกและคืนค่าค่าที่จำได้เป็น savedInstanceState
เปลี่ยน remember
เป็น rememberSaveable
, เรียกใช้แอปในอุปกรณ์ทดสอบ แล้วลองปรับขนาดแอปอีกครั้ง คุณจะสังเกตเห็นว่าสถานะของส่วนหัวที่ขยายได้จะยังคงอยู่ตลอดการปรับขนาดตามที่ควรจะเป็น
6. หลีกเลี่ยงงานที่ทำอยู่เบื้องหลังโดยไม่จำเป็น
คุณได้เห็นวิธีใช้ rememberSaveable
เพื่อเก็บรักษา Composable แล้ว สถานะ UI ภายในผ่านการเปลี่ยนแปลงการกำหนดค่าที่อาจเกิดขึ้นได้บ่อยครั้ง อันเป็นผลมาจากการปรับขนาดหน้าต่างรูปแบบอิสระ อย่างไรก็ตาม แอปควรคงสถานะ UI และตรรกะออกจาก Composable การย้ายการเป็นเจ้าของรัฐไปยัง ViewModel เป็นวิธีรักษาสถานะที่ดีที่สุดวิธีหนึ่งในการปรับขนาด เมื่อย้ายสถานะเป็น ViewModel
คุณอาจพบปัญหากับการทำงานในเบื้องหลังที่ใช้เวลานาน เช่น การเข้าถึงระบบไฟล์ที่มีการใช้จำนวนมาก หรือการเรียกเครือข่ายซึ่งจำเป็นต่อการเริ่มต้นหน้าจอของคุณ
หากต้องการดูตัวอย่างประเภทของปัญหาที่อาจพบ ให้เพิ่มคำสั่งบันทึกลงในเมธอด initializeUIState
ใน ReplyViewModel
fun initializeUIState() {
Log.d("resizing-codelab", "initializeUIState() called in the viewmodel")
val mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
ตอนนี้ให้เรียกใช้แอปในอุปกรณ์ทดสอบ และลองปรับขนาดหน้าต่างของแอปหลายๆ ครั้ง
เมื่อดูที่ Logcat คุณจะเห็นว่าแอปแสดงวิธีการเริ่มต้นทำงานหลายครั้ง ซึ่งอาจเป็นปัญหากับงานที่คุณต้องการเรียกใช้เพียงครั้งเดียวเพื่อเริ่มต้น UI ของคุณ การเรียกใช้เครือข่ายเพิ่มเติม, I/O ไฟล์ หรืองานอื่นๆ อาจขัดขวางประสิทธิภาพของอุปกรณ์ และทำให้เกิดปัญหาอื่นๆ ที่ไม่ตั้งใจ
เพื่อหลีกเลี่ยงการทำงานในเบื้องหลังที่ไม่จำเป็น ให้นำการโทรไปยัง initializeUIState()
ออกจากเมธอด onCreate()
ของกิจกรรม แต่ให้เริ่มต้นข้อมูลในเมธอด init
ของ ViewModel
แทน วิธีนี้ช่วยให้มั่นใจว่าวิธีการเริ่มต้นจะทำงานเพียงครั้งเดียว เมื่อมีการสร้างอินสแตนซ์ ReplyViewModel
เป็นครั้งแรก
init {
initializeUIState()
}
ลองเรียกใช้แอปอีกครั้ง แล้วคุณจะเห็นงานเริ่มต้นการจำลองที่ไม่จำเป็นทำงานเพียงครั้งเดียว ไม่ว่าคุณจะปรับขนาดหน้าต่างแอปกี่ครั้งก็ตาม เนื่องจาก ViewModels คงอยู่หลังวงจรของ Activity
การเรียกใช้โค้ดเริ่มต้นเพียงครั้งเดียวเมื่อสร้าง ViewModel
จะช่วยให้เราแยกโค้ดออกจากกิจกรรม Activity
ต่างๆ และป้องกันการทำงานที่ไม่จำเป็น หากนี่เป็นการเรียกใช้เซิร์ฟเวอร์ที่มีราคาแพงหรือการดำเนินการ I/O ไฟล์ที่หนักเพื่อเริ่มต้นใช้งาน UI ของคุณ คุณจะประหยัดทรัพยากรจำนวนมากและปรับปรุงประสบการณ์ของผู้ใช้ได้
7. ยินดีด้วย!
สำเร็จแล้ว! ทำได้ดีมาก ตอนนี้คุณได้นำแนวทางปฏิบัติแนะนำบางอย่างไปใช้ในการอนุญาตให้แอป Android ปรับขนาดได้ดีใน ChromeOS รวมถึงสภาพแวดล้อมแบบหลายหน้าจอและแบบหลายหน้าจอแบบอื่นๆ แล้ว
ซอร์สโค้ดตัวอย่าง
โคลนที่เก็บจาก GitHub
git clone https://github.com/android/large-screen-codelabs/
...หรือดาวน์โหลดไฟล์ ZIP ของที่เก็บและแตกไฟล์