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

การแคชและประสิทธิภาพ

พื้นฐานการแคชในหน่วยความจำและดิสก์

การแคชและประสิทธิภาพ เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

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

  1. การโหลดรูปภาพด้วย Coil
  2. ตัวแทนและสถานะข้อผิดพลาด
  3. การแคชและประสิทธิภาพ
  4. การเล่นเสียงและวิดีโอ
← กลับไปที่ Android Academy