0Pricing
Android Academy · บทเรียน

การควบคุมการจัดองค์ประกอบใหม่

พารามิเตอร์ที่เสถียรและการจัดองค์ประกอบใหม่น้อยลง

การควบคุมการจัดองค์ประกอบใหม่ เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน

การจัดองค์ประกอบใหม่: ปุ่มควบคุมประสิทธิภาพของ Compose

ใน Jetpack Compose UI จะถูกอธิบายด้วยฟังก์ชัน เมื่อสถานะเปลี่ยน Compose จะทำการจัดองค์ประกอบใหม่: เรียกใช้คอมโพสซิเบิลที่อ่านสถานะนั้นอีกครั้งเพื่ออัปเดตหน้าจอ

การจัดองค์ประกอบใหม่เป็นเรื่องปกติและมีต้นทุนต่ำเมื่อกำหนดขอบเขตไว้อย่างเหมาะสม แต่เมื่อเกิดขึ้นบ่อยเกินไปหรือครอบคลุมทรีมากเกินไป ก็จะกลายเป็นสาเหตุอันดับ 1 ของอาการกระตุกใน Compose บทเรียนนี้จะสอนให้คุณทำให้การจัดองค์ประกอบใหม่มีขนาดเล็กและเกิดขึ้นไม่บ่อย

ดูจำนวนครั้งของการจัดองค์ประกอบใหม่

ก่อนแก้ไข ให้เริ่มจากการวัดผล เครื่องมือตรวจสอบเค้าโครงใน Android Studio จะแสดงจำนวนครั้งของการจัดองค์ประกอบใหม่ของคอมโพสซิเบิลแต่ละรายการแบบเรียลไทม์ คุณยังใช้เมตริกของคอมไพเลอร์ Compose หรือตัวนับสำหรับแก้ไขข้อผิดพลาดแบบง่ายได้

เคล็ดลับง่าย ๆ คือ SideEffect จะเพิ่มค่าที่อ้างอิงขึ้นหนึ่งทุกครั้งที่คอมโพสซิเบิลถูกจัดองค์ประกอบใหม่ คุณจึงบันทึกความผิดปกติระหว่างการพัฒนาได้

@Composable
fun RecompositionCounter(tag: String) {
    val count = remember { mutableStateOf(0) }
    SideEffect { count.value++ }
    Log.d("Recompose", "$tag recomposed ${count.value} times")
}

// Drop RecompositionCounter("PriceLabel") inside a composable
// to watch how often it re-runs while you interact.

อ่านสถานะให้ช้าที่สุดเท่าที่ทำได้

Compose จะจัดองค์ประกอบใหม่เฉพาะคอมโพสซิเบิลที่อ่านค่าของสถานะนั้น หากองค์ประกอบแม่อ่านสถานะ ทั้งองค์ประกอบแม่จะถูกจัดองค์ประกอบใหม่ แต่หากมีเพียงองค์ประกอบลูกขนาดเล็กที่อ่าน ก็จะจัดองค์ประกอบใหม่เฉพาะองค์ประกอบลูกนั้น

ดังนั้น ให้เลื่อนการอ่านสถานะลงไปในทรี ในเวอร์ชันที่ไม่ดี Column ทั้งหมดจะถูกจัดองค์ประกอบใหม่ทุกครั้งที่มีการเปลี่ยนค่า ส่วนเวอร์ชันที่ดีจะแยกการเปลี่ยนแปลงไว้เฉพาะป้ายกำกับ

// BAD: Column reads `seconds`, so everything recomposes each second
@Composable
fun TimerBad(seconds: Int) {
    Column {
        ExpensiveHeader()
        Text("Elapsed: $seconds")
    }
}

// GOOD: only the Text reads the value via a lambda
@Composable
fun TimerGood(seconds: () -> Int) {
    Column {
        ExpensiveHeader()
        Text("Elapsed: ${seconds()}")
    }
}

เลื่อนการอ่านด้วยแลมบ์ดา

รูปแบบที่ทรงพลังคือ แทนที่จะส่งค่าที่เปลี่ยนบ่อย ให้ส่งแลมบ์ดาที่คืนค่าดังกล่าว คอมโพสซิเบิลที่เรียกแลมบ์ดาในท้ายที่สุดจะเป็นเพียงตัวเดียวที่ถูกจัดองค์ประกอบใหม่

นี่คือเหตุผลที่ Modifier.offset { ... } และ graphicsLayer { ... } รับแลมบ์ดา: ค่าการเลื่อน/แอนิเมชันเปลี่ยนทุกเฟรม และแลมบ์ดาจะกันไม่ให้เกิดการจัดองค์ประกอบใหม่ในระยะการจัดวางทั้งหมด

// Passing the value: parent recomposes every frame of scroll
Box(Modifier.offset(y = scrollOffset.dp))

// Passing a lambda: skips recomposition, updates in the layout phase
Box(Modifier.offset { IntOffset(x = 0, y = scrollOffset.roundToInt()) })

// Same idea for alpha/scale during animation:
Image(
    painter = painter,
    contentDescription = null,
    modifier = Modifier.graphicsLayer { alpha = animatedAlpha() }
)

ความเสถียร: เหตุใด Compose จึงข้ามได้

Compose สามารถข้ามการจัดองค์ประกอบใหม่ของคอมโพสซิเบิลได้ หากพารามิเตอร์ทั้งหมดมีความเสถียรและไม่เปลี่ยนแปลง ชนิดข้อมูลจะมีความเสถียรเมื่อ Compose เชื่อถือได้ว่า equals สะท้อนการเปลี่ยนแปลงจริง และฟิลด์สาธารณะจะไม่เปลี่ยนแปลงโดยที่ไม่รู้ตัว

  • มีความเสถียร: ชนิดข้อมูลพื้นฐาน String คลาสข้อมูลที่เปลี่ยนแปลงไม่ได้ และ State
  • ไม่เสถียร: อินเทอร์เฟซ List/Map คลาสที่มีฟิลด์ var และชนิดข้อมูลจากโมดูลที่ไม่มีคอมไพเลอร์

พารามิเตอร์ที่ไม่เสถียรจะบังคับให้เกิดการจัดองค์ประกอบใหม่แม้ไม่มีสิ่งใดเปลี่ยนแปลง

// UNSTABLE: List is an interface; Compose cannot assume immutability,
// so UserList recomposes even if the contents are identical.
@Composable
fun UserList(users: List<User>) { /* ... */ }

data class User(val id: Long, val name: String) // stable: all vals

ทำให้พารามิเตอร์มีความเสถียร

วิธีแก้ไขทั่วไปสองอย่างสำหรับพารามิเตอร์ที่ไม่เสถียร:

  • ใช้คอลเลกชันที่เปลี่ยนแปลงไม่ได้จาก kotlinx.collections.immutable (เช่น ImmutableList) ซึ่ง Compose จะถือว่ามีความเสถียร
  • ใส่คำกำกับให้คลาสที่คุณควบคุมด้วย @Immutable หรือ @Stable เพื่อรับรองกับ Compose ว่าคลาสจะไม่เปลี่ยนแปลง

ตอนนี้ Compose จึงสามารถข้ามการจัดองค์ประกอบใหม่ได้อย่างปลอดภัยเมื่อส่งอินสแตนซ์เดิมอีกครั้ง

import kotlinx.collections.immutable.ImmutableList
import androidx.compose.runtime.Immutable

@Immutable
data class UiState(
    val title: String,
    val users: ImmutableList<User>
)

// Stable parameter -> Compose can skip this when state is unchanged
@Composable
fun UserList(users: ImmutableList<User>) { /* ... */ }

remember: อย่าคำนวณใหม่ทุกเฟรม

คอมโพสซิเบิลอาจทำงานหลายครั้ง การคำนวณใด ๆ ที่ไม่ใช่เรื่องง่ายซึ่งทำโดยตรงในเนื้อหาจะทำงานในทุกครั้งที่จัดองค์ประกอบใหม่ ให้ครอบการคำนวณด้วย remember เพื่อให้คำนวณใหม่เฉพาะเมื่อคีย์เปลี่ยน

สำหรับค่าที่ได้จากสถานะอื่น ควรใช้ derivedStateOf ซึ่งจะส่งค่าใหม่เฉพาะเมื่อผลลัพธ์ที่คำนวณได้เปลี่ยนจริง

// Recomputes the sorted list only when `items` changes
val sorted = remember(items) { items.sortedBy { it.name } }

// derivedStateOf: only triggers readers when the BOOLEAN flips,
// not on every scroll pixel
val showButton by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
if (showButton) ScrollToTopButton()

คีย์ที่เสถียรในรายการแบบ Lazy

ใน LazyColumn/LazyRow ให้กำหนด คีย์ ที่เสถียรให้แต่ละรายการ หากไม่มีคีย์ การแทรกหรือเรียงลำดับรายการใหม่จะบังคับให้ Compose จัดองค์ประกอบใหม่และวัดรายการที่ไม่ได้เปลี่ยนจริงซ้ำ เพราะ Compose ติดตามรายการตามตำแหน่ง

เมื่อมีคีย์ที่เสถียร Compose จะจับคู่รายการระหว่างการอัปเดตและข้ามรายการที่ไม่เปลี่ยนแปลงได้

LazyColumn {
    items(
        items = users,
        key = { user -> user.id }   // stable identity
    ) { user ->
        UserRow(user)
    }
}
// Now adding a user at the top reuses existing rows
// instead of recomposing the whole list.

ยกสถานะขึ้น ส่งเหตุการณ์ขึ้นด้านบน

พารามิเตอร์แลมบ์ดาที่ไม่เสถียรอาจทำให้การข้ามใช้ไม่ได้เช่นกัน หากมีการสร้างอินสแตนซ์แลมบ์ดาใหม่ทุกครั้งที่จัดองค์ประกอบใหม่ ทำให้การเรียกกลับมีความเสถียรด้วยการจดจำไว้หรืออ้างอิงฟังก์ชันที่เสถียร

เมื่อทำร่วมกับการยกสถานะขึ้น (สถานะอยู่ในผู้เรียก เหตุการณ์ไหลขึ้นด้านบน) วิธีนี้จะทำให้คอมโพสซิเบิลปลายทางมีต้นทุนต่ำและสามารถถูกข้ามได้

@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
    TextField(value = query, onValueChange = onQueryChange)
}

// In the caller, a remembered lambda keeps the reference stable:
val onChange = remember { { newValue: String -> viewModel.setQuery(newValue) } }
SearchBar(query = query, onQueryChange = onChange)

หลีกเลี่ยงการอ่านสถานะการเลื่อน/แอนิเมชันในระดับสูงเกินไป

ข้อผิดพลาดที่พบบ่อยคือการอ่านสถานะที่เปลี่ยนเร็ว (ออฟเซ็ตการเลื่อน ความคืบหน้าของแอนิเมชัน) ในคอมโพสซิเบิลระดับสูง ซึ่งบังคับให้ทรีย่อยทั้งหมดถูกจัดองค์ประกอบใหม่ทุกเฟรม

ให้การอ่านดังกล่าวอยู่ภายในแลมบ์ดาของ Modifier (offset {}, graphicsLayer {}, drawBehind {}) เพื่อให้งานเกิดขึ้นในระยะการจัดวางหรือการวาด ไม่ใช่การจัดองค์ประกอบใหม่ นี่คือความแตกต่างระหว่างการเลื่อนที่ลื่นไหล 60fps กับการเลื่อนที่สะดุด

// Animated color used only for drawing -> stay in the draw phase
Box(
    Modifier.drawBehind {
        drawRect(color = animatedColor())  // lambda read, no recomposition
    }
)

รายการตรวจสอบการจัดองค์ประกอบใหม่

เมื่อหน้าจอรู้สึกกระตุกระหว่างการโต้ตอบ ให้ตรวจสอบรายการนี้:

  • พารามิเตอร์มีความเสถียรหรือไม่ (ข้อมูลที่เปลี่ยนแปลงไม่ได้, ImmutableList)
  • คุณอ่านสถานะที่เปลี่ยนเร็วในระดับต่ำที่สุดเท่าที่ทำได้หรือไม่ โดยเหมาะที่สุดคืออ่านในแลมบ์ดาของตัวปรับแต่ง
  • รายการแบบ Lazy มีคีย์ที่เสถียรหรือไม่
  • การคำนวณที่มีต้นทุนสูงอยู่หลัง remember / derivedStateOf หรือไม่
  • เครื่องมือตรวจสอบเค้าโครงยืนยันหรือไม่ว่าจำนวนครั้งลดลงจริง

ทุกช่องที่คุณทำเครื่องหมายจะช่วยกำจัดงานที่สูญเปล่าจากทุกเฟรม

ตรวจสอบอย่างรวดเร็ว

คุณส่ง List<User> ให้คอมโพสซิเบิลและสังเกตว่ามันถูกจัดองค์ประกอบใหม่แม้ข้อมูลจะไม่เปลี่ยนแปลง วิธีแก้โดยตรงที่สุดคืออะไร

สรุป: การจัดองค์ประกอบใหม่ที่เล็กลงและเกิดน้อยลง

คุณได้เรียนรู้วิธีควบคุมการจัดองค์ประกอบใหม่ ซึ่งเป็นปัญหาด้านประสิทธิภาพอันดับต้น ๆ ของ Compose:

  • วัดจำนวนครั้งด้วยเครื่องมือตรวจสอบเค้าโครงหรือตัวนับสำหรับแก้ไขข้อผิดพลาด
  • อ่านสถานะในระดับต่ำที่สุดเท่าที่ทำได้ และเลื่อนการอ่านด้วยแลมบ์ดา
  • ทำให้พารามิเตอร์มีความเสถียรด้วยข้อมูลที่เปลี่ยนแปลงไม่ได้และ @Immutable/ImmutableList
  • ใช้ remember และ derivedStateOf เพื่อหลีกเลี่ยงการคำนวณใหม่ทุกเฟรม
  • กำหนดคีย์ที่เสถียรให้รายการแบบ Lazy และให้การอ่านสถานะที่เปลี่ยนเร็วอยู่ในแลมบ์ดาของตัวปรับแต่ง

ต่อไป เราจะเปลี่ยนจาก CPU/การจัดองค์ประกอบใหม่ไปสู่หน่วยความจำ เพื่อค้นหาและแก้ไขการรั่วไหล

คำถามที่พบบ่อย

บทเรียน “การควบคุมการจัดองค์ประกอบใหม่” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การควบคุมการจัดองค์ประกอบใหม่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การควบคุมการจัดองค์ประกอบใหม่”

พารามิเตอร์ที่เสถียรและการจัดองค์ประกอบใหม่น้อยลง คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การควบคุมการจัดองค์ประกอบใหม่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม

ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การวัดประสิทธิภาพ
  2. การควบคุมการจัดองค์ประกอบใหม่
  3. หน่วยความจำรั่วและวิธีแก้ไข
  4. โปรไฟล์การเริ่มต้นและโปรไฟล์พื้นฐาน
← กลับไปที่ Android Academy