การแคชและประสิทธิภาพ
พื้นฐานการแคชในหน่วยความจำและดิสก์
การแคชและประสิทธิภาพ เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดการแคชจึงสำคัญ
การดาวน์โหลดและถอดรหัสรูปภาพใช้ทรัพยากรมาก ทั้งแบนด์วิดท์ แบตเตอรี่ และ CPU หากคุณดึงรูปภาพเดิมใหม่ทุกครั้งที่เลื่อนรายการ แอปจะรู้สึกช้าและใช้ดาต้ามากเกินไป
การแคช จะเก็บรูปภาพที่ถอดรหัสแล้วและไบต์ที่ดาวน์โหลดไว้ ทำให้คำขอครั้งถัดไปแทบจะแสดงผลได้ทันที โดยค่าเริ่มต้น Coil มีแคชสองประเภท ได้แก่ แคชในหน่วยความจำ (รวดเร็ว เก็บบิตแมปที่ถอดรหัสแล้ว) และแคชในดิสก์ (คงอยู่ถาวร เก็บไบต์ดิบ)
บทเรียนนี้จะอธิบายการทำงานของแคชทั้งสองประเภทและวิธีปรับแต่ง
สองชั้น: หน่วยความจำและดิสก์
Coil ตรวจสอบแคชตามลำดับดังนี้:
- แคชในหน่วยความจำ: เก็บบิตแมปที่ถอดรหัสแล้วและเพิ่งใช้งานไว้ใน RAM หากพบข้อมูลที่นี่จะแสดงผลทันทีโดยไม่ต้องถอดรหัส
- แคชในดิสก์: เก็บไบต์ต้นฉบับที่ดาวน์โหลดไว้ในอุปกรณ์ หากพบข้อมูลที่นี่จะข้ามการเชื่อมต่อเครือข่าย แต่ยังต้องถอดรหัส
- เครือข่าย: เส้นทางที่ช้าที่สุด ใช้เมื่อไม่พบข้อมูลในแคชทั้งสองประเภท
หน่วยความจำทำงานเร็วแต่มีขนาดเล็กและจะถูกล้างเมื่อแอปปิดลง ส่วนดิสก์มีขนาดใหญ่กว่าและยังคงข้อมูลไว้หลังเปิดแอปใหม่
การกำหนดค่า ImageLoader
แคชอยู่บน ImageLoader ซึ่งเป็นกลไกเบื้องหลังคำขอทุกคำขอ โดยทั่วไปคุณควรสร้าง loader แบบกำหนดเองหนึ่งตัวแล้วนำกลับมาใช้ซ้ำ ในที่นี้เราจะกำหนดขนาดแคชในหน่วยความจำเป็นเปอร์เซ็นต์ของ RAM ที่พร้อมใช้งาน และกำหนดแคชในดิสก์ด้วยงบประมาณไบต์แบบตายตัว
loader แบบ Singleton ช่วยหลีกเลี่ยงการมีแคชซ้ำซ้อนและการใช้หน่วยความจำโดยเปล่าประโยชน์
import android.content.Context
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import okio.Path.Companion.toOkioPath
fun newImageLoader(context: Context): ImageLoader =
ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, 0.25) // 25% of app RAM
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache").toOkioPath())
.maxSizeBytes(50L * 1024 * 1024) // 50 MB
.build()
}
.build()การจัดเตรียม Loader แบบ Singleton
Coil 3 ให้คุณกำหนด loader เดียวสำหรับทั้งแอปพลิเคชันได้ โดยใช้อินเทอร์เฟซ SingletonImageLoader.Factory กับคลาส Application ของคุณ จากนั้น AsyncImage ทุกตัวจะใช้ loader นี้โดยอัตโนมัติ โดยไม่จำเป็นต้องส่ง loader ให้ทุกครั้ง
import android.app.Application
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.crossfade(true)
.build()
}การควบคุมนโยบายแคช
แต่ละคำขอสามารถเลือกเปิดหรือปิดการแคชได้ด้วย memoryCachePolicy และ diskCachePolicy นโยบายที่ใช้ได้คือ ENABLED, READ_ONLY, WRITE_ONLY หรือ DISABLED
ตัวอย่างเช่น ปิดแคชในหน่วยความจำสำหรับรูปภาพขนาดใหญ่ที่ใช้เพียงครั้งเดียวและไม่ต้องการเก็บไว้ใน RAM หรือปิดแคชในดิสก์สำหรับเนื้อหาที่มีความละเอียดอ่อน
import android.content.Context
import coil3.request.CachePolicy
import coil3.request.ImageRequest
fun noMemoryCacheRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.build()ปรับขนาด: อย่าถอดรหัสบิตแมปขนาดใหญ่เกินจำเป็น
ข้อผิดพลาดด้านประสิทธิภาพที่ใหญ่ที่สุดคือการถอดรหัสรูปภาพขนาด 4000x3000 ให้เป็นภาพขนาดย่อ 100dp ซึ่งทำให้เสียทั้งหน่วยความจำและเวลา โดยค่าเริ่มต้น Coil จะปรับขนาดบิตแมปตามขอบเขตที่วัดได้ของ composable ดังนั้น AsyncImage ขนาดเล็กจึงถอดรหัสบิตแมปขนาดเล็ก
คุณสามารถบังคับขนาดเป้าหมายที่ชัดเจนได้ด้วย size() ในคำขอ เมื่อทราบขนาดที่ต้องการ
import android.content.Context
import coil3.request.ImageRequest
import coil3.size.Size
fun thumbnailRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.size(Size(200, 200)) // decode at most 200x200
.build()คีย์แคชที่คงที่
Coil สร้างคีย์แคชจากข้อมูลคำขอ หาก URL ของคุณมีพารามิเตอร์คำค้นหาที่เปลี่ยนแปลงได้ เช่น โทเค็นที่มีลายเซ็น รูปภาพเดียวกันจะได้คีย์ต่างกันและจะไม่พบข้อมูลในแคชเลย
กำหนด memoryCacheKey และ diskCacheKey อย่างชัดเจนโดยอิงจากส่วนที่คงที่ เช่น รหัสรูปภาพ เพื่อให้การแคชทำงานได้
import android.content.Context
import coil3.request.ImageRequest
fun stableKeyRequest(context: Context, id: String, signedUrl: String) =
ImageRequest.Builder(context)
.data(signedUrl)
.memoryCacheKey("image_$id")
.diskCacheKey("image_$id")
.build()โหลดรูปภาพล่วงหน้า
เพื่อให้การใช้งานราบรื่นยิ่งขึ้น คุณสามารถเตรียมแคชให้อุ่นก่อนแสดงรูปภาพได้ เช่น โหลดหน้าถัดไปของฟีดล่วงหน้า ใช้ enqueue ของ loader พร้อมคำขอที่ไม่มีเป้าหมาย
เมื่อผู้ใช้เลื่อนไปถึงรูปภาพ รูปภาพนั้นจะอยู่ในแคชแล้วและแสดงผลได้ทันที
import android.content.Context
import coil3.ImageLoader
import coil3.request.ImageRequest
fun preload(context: Context, loader: ImageLoader, urls: List<String>) {
urls.forEach { url ->
val request = ImageRequest.Builder(context)
.data(url)
.build()
loader.enqueue(request)
}
}การล้างแคช
บางครั้งคุณจำเป็นต้องเพิ่มพื้นที่ว่างหรือบังคับให้โหลดข้อมูลใหม่ เช่น หลังจากผู้ใช้อัปเดตอวาตาร์ คุณสามารถล้างแคชในหน่วยความจำ แคชในดิสก์ หรือลบคีย์เพียงรายการเดียวได้
การล้างทั้งหมดใช้ทรัพยากรมาก ดังนั้นหากทำได้ควรลบเฉพาะคีย์ที่ต้องการ
import coil3.ImageLoader
import coil3.memory.MemoryCache
fun refreshAvatar(loader: ImageLoader, key: String) {
// Remove one entry so the next load refetches
loader.memoryCache?.remove(MemoryCache.Key(key))
loader.diskCache?.remove(key)
}
fun clearAll(loader: ImageLoader) {
loader.memoryCache?.clear()
// Disk clearing is heavier; do it sparingly
}Crossfade กับการพบข้อมูลในแคช
เอฟเฟกต์ Crossfade ดูดีเมื่อโหลดผ่านเครือข่าย แต่เมื่อรูปภาพมาจากแคชในหน่วยความจำโดยตรงและควรแสดงได้ทันที เอฟเฟกต์นี้อาจทำให้รู้สึกช้า Coil จัดการเรื่องนี้อย่างชาญฉลาด โดยค่าเริ่มต้นจะข้ามภาพเคลื่อนไหว Crossfade เมื่อพบข้อมูลในแคชหน่วยความจำ และจะแสดงภาพเคลื่อนไหวเฉพาะรูปภาพที่โหลดเข้ามาจริงเท่านั้น
ดังนั้นคุณจะได้การเปลี่ยนภาพอย่างนุ่มนวลในการโหลดครั้งแรก และการแสดงผลทันทีเมื่อเลื่อนกลับมาที่รูปภาพในแคช โดยไม่ต้องทำงานเพิ่มเติม
import android.content.Context
import coil3.request.ImageRequest
import coil3.request.crossfade
fun feedRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
// Crossfade plays on real loads; memory-cache hits show instantly
.crossfade(true)
.build()รายการตรวจสอบประสิทธิภาพสำหรับรายการ
เมื่อแสดงรูปภาพจำนวนมากใน LazyColumn หรือกริด ให้ปฏิบัติตามกฎเหล่านี้เพื่อให้การเลื่อนราบรื่น:
- กำหนดขนาดให้รูปภาพแต่ละรูปแบบตายตัว เพื่อให้ Coil ถอดรหัสบิตแมปขนาดเล็ก
- ใช้
ImageLoaderแบบ Singleton เพียงตัวเดียว เพื่อให้แคชใช้งานร่วมกัน - ใช้
ContentScale.Cropเพื่อป้องกันการขยับของเค้าโครง - รักษาคีย์แคชให้คงที่ตลอดการโหลดใหม่
- ปล่อยให้ Coil ยกเลิกคำขอที่อยู่นอกหน้าจอโดยอัตโนมัติ ไม่ต้องพยายามขัดขวางการทำงานนี้
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImage
@Composable
fun PhotoGrid(urls: List<String>) {
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(urls) { url ->
AsyncImage(
model = url,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.aspectRatio(1f)
)
}
}
}ตรวจสอบอย่างรวดเร็ว
Coil จะตรวจสอบแคชก่อนเข้าถึงเครือข่าย แคชใดเมื่อพบข้อมูลแล้วจะเร็วที่สุด เพราะไม่ต้องถอดรหัสเลย
สรุปทบทวน
คุณได้เรียนรู้วิธีที่ Coil ทำให้รูปภาพทำงานได้รวดเร็ว:
- มีแคชสองประเภท ได้แก่ แคชในหน่วยความจำ (ถอดรหัสแล้ว รวดเร็ว ขนาดเล็ก) และแคชในดิสก์ (ไบต์ดิบ คงอยู่ถาวร ขนาดใหญ่กว่า)
- กำหนดขนาดบน
ImageLoaderแบบกำหนดเอง และเปิดให้ใช้งานตัวเดียวผ่านSingletonImageLoader.Factory - ปรับพฤติกรรมของแต่ละคำขอด้วย
memoryCachePolicyและdiskCachePolicy - ถอดรหัสให้มีขนาดเท่ากับการแสดงผลเสมอ และกำหนด
size()อย่างชัดเจนเมื่อจำเป็น - ใช้คีย์แคชที่คงที่ โหลดรูปภาพที่กำลังจะใช้งานล่วงหน้า และล้างเฉพาะคีย์ที่ต้องการเพื่อโหลดข้อมูลใหม่
ถัดไป: ก้าวจากรูปภาพไปสู่การเล่นเสียงและวิดีโอ
คำถามที่พบบ่อย
บทเรียน “การแคชและประสิทธิภาพ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การแคชและประสิทธิภาพ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การแคชและประสิทธิภาพ”
พื้นฐานการแคชในหน่วยความจำและดิสก์ คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การแคชและประสิทธิภาพ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม
ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การโหลดรูปภาพด้วย Coil
- ตัวแทนและสถานะข้อผิดพลาด
- การแคชและประสิทธิภาพ
- การเล่นเสียงและวิดีโอ