เหตุใดจึงต้องแบ่งหน้า
ต้นทุนของการโหลดทุกอย่างพร้อมกัน
เหตุใดจึงต้องแบ่งหน้า เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
โหลดทุกอย่างมีต้นทุนสูง
ลองนึกภาพฟีดที่มีรายการ 50,000 รายการ หากคุณดึงข้อมูลและเก็บทุกรายการไว้พร้อมกัน จะสิ้นเปลืองหน่วยความจำ ทำให้เครือข่ายทำงานช้าลง และทำให้ UI ค้างขณะวิเคราะห์การตอบกลับ
ผู้ใช้ส่วนใหญ่เลื่อนดูเพียงไม่กี่หน้าจอแรกเท่านั้น การแบ่งหน้า หมายถึงการโหลดข้อมูลเป็นส่วนเล็ก ๆ (หน้า) ขณะที่ผู้ใช้เลื่อน แทนที่จะโหลดทั้งหมดตั้งแต่ต้น
ปัญหาในโค้ด
วิธีง่าย ๆ คือโหลดรายการทั้งหมดเข้าไปไว้ในหน่วยความจำ แม้เซิร์ฟเวอร์จะรองรับ แต่การตอบกลับอาจมีขนาดใหญ่มาก และรายการที่แยกวิเคราะห์แล้วอาจกินพื้นที่ฮีปส่วนใหญ่
รูปแบบนี้ไม่สามารถรองรับข้อมูลที่เพิ่มขึ้นได้ดี และเสี่ยงทำให้เกิด OutOfMemoryError เมื่อชุดข้อมูลมีขนาดใหญ่
// Anti-pattern: load the whole table at once
suspend fun loadAllArticles(): List<Article> {
// Could be tens of thousands of rows / megabytes of JSON
return api.getArticles(limit = 50_000)
}
// UI holds ALL of them in memory at once
val articles = loadAllArticles()
LazyColumn {
items(articles) { article -> ArticleRow(article) }
}หน้าตาหนึ่งหน้าจะเป็นอย่างไร
หน้า คือส่วนย่อยขนาดเล็กของชุดข้อมูล โดยมักมี 20–50 รายการ เซิร์ฟเวอร์จะส่งคืนหนึ่งหน้าพร้อมตัวชี้ไปยังหน้าถัดไป ซึ่งอาจเป็นตัวเลข ออฟเซ็ต หรือโทเค็น
แอปจะเก็บไว้ในหน่วยความจำเพียงไม่กี่หน้า และทิ้งหน้าเก่าเมื่อผู้ใช้เลื่อนผ่านไป
data class ArticlePage(
val items: List<Article>,
val nextKey: Int? // null means no more pages
)
// Example REST call returning one page
suspend fun getArticlePage(page: Int, size: Int = 20): ArticlePageทำความรู้จัก Paging 3
Paging 3 คือไลบรารี Jetpack ที่จัดการส่วนที่ซับซ้อนของการแบ่งหน้าให้คุณทั้งหมด:
- ขอหน้าถัดไปเมื่อผู้ใช้เลื่อนเข้าใกล้ท้ายรายการ
- เก็บเฉพาะช่วงของรายการไว้ในหน่วยความจำ
- เปิดเผยสถานะการโหลดและข้อผิดพลาด
- รองรับ coroutine, Flow และ Jetpack Compose ในตัว
การเพิ่มการพึ่งพา
Paging 3 เผยแพร่เป็นอาร์ติแฟกต์แยกกัน ได้แก่ รันไทม์และการเชื่อมต่อกับ Compose ให้เพิ่มอาร์ติแฟกต์เหล่านี้ใน build.gradle.kts ของโมดูล
อาร์ติแฟกต์ paging-compose มีตัวช่วยสำหรับ LazyColumn
// build.gradle.kts (module)
dependencies {
val pagingVersion = "3.3.6"
implementation("androidx.paging:paging-runtime:$pagingVersion")
implementation("androidx.paging:paging-compose:$pagingVersion")
}องค์ประกอบหลักสามส่วน
Paging 3 มีประเภทหลักสามประเภทที่คุณจะใช้ซ้ำแล้วซ้ำอีก:
- PagingSource - รู้วิธีโหลดหนึ่งหน้าจากแหล่งข้อมูล
- Pager - กำหนดค่าและสร้างสตรีมข้อมูลแบบแบ่งหน้า
- PagingData - คอนเทนเนอร์ของรายการ ซึ่งปล่อยออกมาเป็น
Flow
เราจะสร้างแต่ละส่วนในบทเรียนถัดไป
PagingData และ Flow
รีโพซิทอรีของคุณเปิดเผย Flow<PagingData<T>> ส่วน UI จะรวบรวมข้อมูลและแสดงรายการที่โหลดอยู่ในขณะนั้น
คุณไม่ต้องประกอบรายการทั้งหมดด้วยตนเอง เพราะ Paging จะสตรีมรายการเข้าสู่ UI ตามต้องการ
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ArticleRepository(private val api: ArticleApi) {
fun articles(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20)
) {
ArticlePagingSource(api)
}.flow
}PagingConfig ควบคุมช่วงข้อมูล
PagingConfig ใช้ปรับวิธีที่ Paging โหลดและเก็บข้อมูล:
pageSize- จำนวนรายการต่อคำขอหนึ่งหน้าprefetchDistance- ระยะห่างจากขอบที่ใช้เรียกการโหลดครั้งถัดไปenablePlaceholders- แสดงช่องตัวแทนสำหรับรายการที่ยังไม่ได้โหลดmaxSize- จำกัดจำนวนรายการที่เก็บไว้ในหน่วยความจำ
import androidx.paging.PagingConfig
val config = PagingConfig(
pageSize = 20,
prefetchDistance = 5,
enablePlaceholders = false,
maxSize = 100
)เครือข่ายหรือฐานข้อมูล หรือทั้งสองอย่าง
PagingSource สามารถดึงข้อมูลจาก REST API ฐานข้อมูล Room หรือแหล่งข้อมูลแบบกำหนดเองใด ๆ
หากต้องการประสบการณ์แบบออฟไลน์เป็นหลักอย่างแท้จริง ให้รวมทั้งสองอย่างด้วย RemoteMediator (ซึ่งจะกล่าวถึงในบทเรียนสุดท้าย): เครือข่ายจะเติมข้อมูลลงในฐานข้อมูลภายในเครื่อง และ UI จะแบ่งหน้าข้อมูลจากฐานข้อมูลนั้น
เลื่อนดูได้ลื่นไหล ใช้หน่วยความจำน้อยลง
เมื่อใช้การแบ่งหน้า การเลื่อนจะยังคงลื่นไหล เพราะแต่ละคำขอมีขนาดเล็ก และช่วงข้อมูลในหน่วยความจำถูกจำกัดด้วย maxSize
ตัวบ่งชี้การโหลดและข้อผิดพลาดมีให้ผ่าน LoadState โดยไม่ต้องเขียนเพิ่ม คุณจึงแสดงตัวหมุนด้านล่างหรือปุ่มลองใหม่ได้โดยไม่ต้องจัดการสถานะด้วยตนเอง
// LoadState exposes loading/error per direction
when (val state = adapterLoadState.append) {
is LoadState.Loading -> showBottomSpinner()
is LoadState.Error -> showRetry(state.error)
is LoadState.NotLoading -> hideBottomSpinner()
}เมื่อใดที่ควรใช้การแบ่งหน้า
เลือกใช้ Paging 3 เมื่อ:
- ชุดข้อมูลมีขนาดใหญ่หรือไม่มีขอบเขตแน่นอน เช่น ฟีด ผลการค้นหา หรือประวัติแชต
- ข้อมูลมาจาก API เครือข่ายที่ส่งคืนข้อมูลเป็นหน้าหรือเคอร์เซอร์
- คุณต้องการ UI สำหรับการโหลดและข้อผิดพลาดโดยอัตโนมัติ รวมถึงการควบคุมหน่วยความจำ
สำหรับรายการสั้น ๆ ที่มีจำนวนตายตัว เช่น เมนูการตั้งค่า การใช้ LazyColumn ธรรมดาจะง่ายกว่าและเพียงพออย่างสมบูรณ์
ตรวจสอบความเข้าใจ
ประโยชน์หลักของการใช้ Paging 3 กับรายการขนาดใหญ่คืออะไร
สรุป: เหตุผลที่ควรใช้การแบ่งหน้า
คุณได้เรียนรู้ว่าการโหลดทุกอย่างพร้อมกันส่งผลเสียต่อประสิทธิภาพและหน่วยความจำอย่างไร และ Paging 3 แก้ปัญหานี้อย่างไร
- โหลดข้อมูลเป็นหน้าขนาดเล็กขณะผู้ใช้เลื่อนดู
- องค์ประกอบหลักสามส่วนคือ PagingSource, Pager และ PagingData
PagingConfigใช้ปรับขนาดหน้า การโหลดล่วงหน้า และช่วงข้อมูลในหน่วยความจำ- Paging ทำงานร่วมกับ coroutine, Flow และ Compose พร้อมมีสถานะการโหลดและข้อผิดพลาดให้โดยไม่ต้องเขียนเพิ่ม
ถัดไป: การสร้าง PagingSource และเชื่อมต่อเข้ากับ Pager
คำถามที่พบบ่อย
บทเรียน “เหตุใดจึงต้องแบ่งหน้า” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “เหตุใดจึงต้องแบ่งหน้า” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “เหตุใดจึงต้องแบ่งหน้า”
ต้นทุนของการโหลดทุกอย่างพร้อมกัน คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “เหตุใดจึงต้องแบ่งหน้า” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม
ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เหตุใดจึงต้องแบ่งหน้า
- PagingSource และ Pager
- การแบ่งหน้าในรายการ Compose
- RemoteMediator และการแคช