การแคชบิตแมป

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

การโหลดบิตแมปเดียวในอินเทอร์เฟซผู้ใช้ (UI) สามารถทำได้โดยง่าย แต่จะได้รับมากกว่า ซับซ้อนหากคุณต้องโหลดชุดรูปภาพขนาดใหญ่พร้อมกัน ในหลายกรณี (เช่น เช่น ListView, GridView หรือ ViewPager) จำนวนรูปภาพทั้งหมดบนหน้าจอที่รวมกับรูปภาพ อาจเลื่อนหน้าจอไปได้อย่างไม่จำกัด

คอมโพเนนต์ลักษณะนี้จะช่วยประหยัดหน่วยความจําด้วยการนําวิวย่อยกลับมาใช้ใหม่เมื่อออกจากหน้าจอ นอกจากนี้ ตัวเก็บขยะยังช่วยเพิ่มพื้นที่ว่างสำหรับบิตแมปที่โหลดไว้ด้วย ในกรณีที่คุณไม่ได้เก็บข้อมูลอ้างอิงที่มีอายุการใช้งานยาวนานไว้ การดำเนินการนี้เป็นสิ่งที่ดี แต่คุณควรหลีกเลี่ยงการประมวลผลรูปภาพเหล่านี้อย่างต่อเนื่องทุกครั้งที่ปรากฏบนหน้าจอเพื่อให้ UI โหลดได้อย่างรวดเร็วและราบรื่น ความทรงจำ และดิสก์แคชมักมีประโยชน์ในส่วนนี้ โดยช่วยให้คอมโพเนนต์สามารถโหลดรูปภาพที่ประมวลผลแล้วซ้ำได้อย่างรวดเร็ว

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

ใช้แคชหน่วยความจำ

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

หมายเหตุ: ก่อนหน้านี้ การใช้งานแคชหน่วยความจำยอดนิยมคือ อย่างไรก็ตาม SoftReference หรือ WeakReference แคชบิตแมป เราไม่แนะนำให้ทำเช่นนั้น ตั้งแต่ Android 2.3 (API ระดับ 9) เป็นต้นไป ตัวเก็บขยะจะเก็บข้อมูลอ้างอิงแบบอ่อน/แบบอ่อนแออย่างหนักหน่วงมากขึ้น ซึ่งทำให้ข้อมูลอ้างอิงดังกล่าวมีประสิทธิภาพน้อยลง นอกจากนี้ รุ่นก่อนหน้า Android 3.0 (API ระดับ 11) ข้อมูลสนับสนุนของบิตแมปจะเก็บอยู่ในหน่วยความจำในเครื่อง ไม่ได้มีการเผยแพร่ในลักษณะที่คาดเดาได้ ซึ่งอาจทำให้แอปพลิเคชันมีปริมาณเกิน ขีดจำกัดของหน่วยความจำและการขัดข้อง

การเลือกขนาดที่เหมาะสมสำหรับLruCache ขึ้นอยู่กับปัจจัยหลายประการ คุณควรพิจารณา เช่น

  • กิจกรรมและ/หรือแอปพลิเคชันที่เหลือของคุณต้องใช้หน่วยความจำมากเพียงใด
  • จำนวนภาพที่จะแสดงบนหน้าจอพร้อมกันกี่ภาพ จํานวนสิ่งที่จําเป็นต้องพร้อมใช้งานเมื่อพร้อมใช้งาน บนหน้าจอไหม
  • อุปกรณ์มีขนาดหน้าจอและความหนาแน่นของหน้าจอเท่าใด อุปกรณ์ที่มีหน้าจอความหนาแน่นสูงพิเศษ (xhdpi) เช่น Galaxy Nexus จะต้องมีแคชขนาดใหญ่ขึ้นเพื่อเก็บรูปภาพจำนวนเท่าเดิมไว้ในหน่วยความจำเมื่อเทียบกับอุปกรณ์อย่าง Nexus S (hdpi)
  • บิตแมปมีขนาดและการกำหนดค่าใดบ้าง และจะใช้หน่วยความจำเท่าใด ขึ้น?
  • จะมีการเข้าถึงรูปภาพบ่อยแค่ไหน จะมีการเข้าถึงบางรายการบ่อยกว่ารายการอื่นๆ หรือไม่ หากใช่ คุณอาจต้องเก็บบางรายการไว้ในหน่วยความจำตลอดเวลา หรือแม้กระทั่งมีออบเจ็กต์ LruCache หลายรายการสำหรับกลุ่มบิตแมปต่างๆ
  • คุณรักษาความสมดุลระหว่างคุณภาพกับปริมาณได้ไหม บางครั้งการจัดเก็บบิตแมปคุณภาพต่ำจำนวนมากอาจมีประโยชน์มากกว่า เนื่องจากอาจโหลดเวอร์ชันที่มีคุณภาพสูงขึ้นในอีกงานเบื้องหลัง

ไม่มีขนาดหรือสูตรเฉพาะที่เหมาะสำหรับแอปพลิเคชันทั้งหมด คุณจำเป็นต้องวิเคราะห์ และหาโซลูชันที่เหมาะสม แคชที่มีขนาดเล็กเกินไปจะทำให้เกิดค่าใช้จ่ายเพิ่มเติมกับ ไม่มีประโยชน์ แคชที่มีขนาดใหญ่เกินไปอาจทำให้เกิดข้อยกเว้น java.lang.OutOfMemory อีกครั้ง และปล่อยให้แอปมีหน่วยความจำเหลืออยู่น้อย

ตัวอย่างการตั้งค่า LruCache สำหรับบิตแมปมีดังนี้

Kotlin

private lateinit var memoryCache: LruCache<String, Bitmap>

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()

    // Use 1/8th of the available memory for this memory cache.
    val cacheSize = maxMemory / 8

    memoryCache = object : LruCache<String, Bitmap>(cacheSize) {

        override fun sizeOf(key: String, bitmap: Bitmap): Int {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.byteCount / 1024
        }
    }
    ...
}

Java

private LruCache<String, Bitmap> memoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    memoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        memoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return memoryCache.get(key);
}

หมายเหตุ: ในตัวอย่างนี้ 1 ใน 8 ของหน่วยความจำของแอปพลิเคชัน ที่จัดสรรสำหรับแคชของเรา สำหรับอุปกรณ์ปกติ/hdpi มีขนาดอย่างน้อยประมาณ 4 MB (32/8) เต็ม หน้าจอ GridView ที่เต็มไปด้วยรูปภาพในอุปกรณ์ที่มีความละเอียด 800x480 ใช้ประมาณ 1.5 MB (800*480*4 ไบต์) ดังนั้นจึงแคชรูปภาพอย่างน้อย 2.5 หน้าใน ความทรงจำ

ขณะโหลดบิตแมปลงใน ImageView LruCache จะได้รับการตรวจสอบก่อน หากพบรายการ ระบบจะใช้รายการนั้นในการอัปเดต ImageView ทันที มิเช่นนั้นระบบจะสร้างชุดข้อความในเบื้องหลังเพื่อประมวลผลรูปภาพ

Kotlin

fun loadBitmap(resId: Int, imageView: ImageView) {
    val imageKey: String = resId.toString()

    val bitmap: Bitmap? = getBitmapFromMemCache(imageKey)?.also {
        mImageView.setImageBitmap(it)
    } ?: run {
        mImageView.setImageResource(R.drawable.image_placeholder)
        val task = BitmapWorkerTask()
        task.execute(resId)
        null
    }
}

Java

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}

นอกจากนี้ BitmapWorkerTask ยังต้อง อัปเดตเพื่อเพิ่มรายการในแคชหน่วยความจำ:

Kotlin

private inner class BitmapWorkerTask : AsyncTask<Int, Unit, Bitmap>() {
    ...
    // Decode image in background.
    override fun doInBackground(vararg params: Int?): Bitmap? {
        return params[0]?.let { imageId ->
            decodeSampledBitmapFromResource(resources, imageId, 100, 100)?.also { bitmap ->
                addBitmapToMemoryCache(imageId.toString(), bitmap)
            }
        }
    }
    ...
}

Java

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

ใช้ดิสก์แคช

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

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

หมายเหตุ: ContentProvider อาจมากกว่า ตำแหน่งที่เหมาะสมในการจัดเก็บรูปภาพที่แคชไว้ หากมีการเข้าถึงบ่อยครั้งยิ่งขึ้น ตัวอย่างเช่น ใน แอปพลิเคชันแกลเลอรีรูปภาพ

โค้ดตัวอย่างของคลาสนี้ใช้การติดตั้งใช้งาน DiskLruCache ที่ดึงมาจาก แหล่งที่มา Android ตัวอย่างโค้ดที่อัปเดตซึ่งเพิ่มแคชดิสก์นอกเหนือจากแคชหน่วยความจำที่มีอยู่มีดังนี้

Kotlin

private const val DISK_CACHE_SIZE = 1024 * 1024 * 10 // 10MB
private const val DISK_CACHE_SUBDIR = "thumbnails"
...
private var diskLruCache: DiskLruCache? = null
private val diskCacheLock = ReentrantLock()
private val diskCacheLockCondition: Condition = diskCacheLock.newCondition()
private var diskCacheStarting = true

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    val cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR)
    InitDiskCacheTask().execute(cacheDir)
    ...
}

internal inner class InitDiskCacheTask : AsyncTask<File, Void, Void>() {
    override fun doInBackground(vararg params: File): Void? {
        diskCacheLock.withLock {
            val cacheDir = params[0]
            diskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE)
            diskCacheStarting = false // Finished initialization
            diskCacheLockCondition.signalAll() // Wake any waiting threads
        }
        return null
    }
}

internal inner class  BitmapWorkerTask : AsyncTask<Int, Unit, Bitmap>() {
    ...

    // Decode image in background.
    override fun doInBackground(vararg params: Int?): Bitmap? {
        val imageKey = params[0].toString()

        // Check disk cache in background thread
        return getBitmapFromDiskCache(imageKey) ?:
                // Not found in disk cache
                decodeSampledBitmapFromResource(resources, params[0], 100, 100)
                        ?.also {
                            // Add final bitmap to caches
                            addBitmapToCache(imageKey, it)
                        }
    }
}

fun addBitmapToCache(key: String, bitmap: Bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        memoryCache.put(key, bitmap)
    }

    // Also add to disk cache
    synchronized(diskCacheLock) {
        diskLruCache?.apply {
            if (!containsKey(key)) {
                put(key, bitmap)
            }
        }
    }
}

fun getBitmapFromDiskCache(key: String): Bitmap? =
        diskCacheLock.withLock {
            // Wait while disk cache is started from background thread
            while (diskCacheStarting) {
                try {
                    diskCacheLockCondition.await()
                } catch (e: InterruptedException) {
                }

            }
            return diskLruCache?.get(key)
        }

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
fun getDiskCacheDir(context: Context, uniqueName: String): File {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    val cachePath =
            if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()
                    || !isExternalStorageRemovable()) {
                context.externalCacheDir.path
            } else {
                context.cacheDir.path
            }

    return File(cachePath + File.separator + uniqueName)
}

Java

private DiskLruCache diskLruCache;
private final Object diskCacheLock = new Object();
private boolean diskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (diskCacheLock) {
            File cacheDir = params[0];
            diskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            diskCacheStarting = false; // Finished initialization
            diskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        memoryCache.put(key, bitmap);
    }

    // Also add to disk cache
    synchronized (diskCacheLock) {
        if (diskLruCache != null && diskLruCache.get(key) == null) {
            diskLruCache.put(key, bitmap);
        }
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (diskCacheLock) {
        // Wait while disk cache is started from background thread
        while (diskCacheStarting) {
            try {
                diskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (diskLruCache != null) {
            return diskLruCache.get(key);
        }
    }
    return null;
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}

หมายเหตุ: แม้แต่การเริ่มต้นแคชดิสก์ก็ต้องอาศัยการดำเนินการของดิสก์ ดังนั้นจึงไม่ควรเกิดขึ้นในเทรดหลัก แต่ก็หมายความว่ามีโอกาส มีการเข้าถึงแคชก่อนการเริ่มต้น ในการแก้ปัญหานี้ ในการใช้งานด้านบน การล็อก ทำให้มั่นใจได้ว่าแอปจะไม่อ่านจากดิสก์แคชจนกว่าแคชจะเสร็จสิ้น ได้เริ่มต้นแล้ว

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

จัดการการเปลี่ยนแปลงการกำหนดค่า

การเปลี่ยนแปลงการกำหนดค่ารันไทม์ เช่น การเปลี่ยนการวางแนวหน้าจอ จะทำให้ Android ทำลายและ รีสตาร์ทกิจกรรมที่ทำงานอยู่ด้วยการกำหนดค่าใหม่ (สำหรับข้อมูลเพิ่มเติมเกี่ยวกับลักษณะการทำงานนี้ ดูการจัดการการเปลี่ยนแปลงรันไทม์) คุณควรหลีกเลี่ยงการประมวลผลรูปภาพทั้งหมดอีกครั้งเพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ราบรื่นและรวดเร็วเมื่อมีการเปลี่ยนแปลงการกําหนดค่า

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

ตัวอย่างการเก็บรักษาออบเจ็กต์ LruCache ไว้ในการกำหนดค่ามีดังนี้ การเปลี่ยนแปลงโดยใช้ Fragment:

Kotlin

private const val TAG = "RetainFragment"
...
private lateinit var mMemoryCache: LruCache<String, Bitmap>

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    val retainFragment = RetainFragment.findOrCreateRetainFragment(supportFragmentManager)
    mMemoryCache = retainFragment.retainedCache ?: run {
        LruCache<String, Bitmap>(cacheSize).also { memoryCache ->
            ... // Initialize cache here as usual
            retainFragment.retainedCache = memoryCache
        }
    }
    ...
}

class RetainFragment : Fragment() {
    var retainedCache: LruCache<String, Bitmap>? = null

    companion object {
        fun findOrCreateRetainFragment(fm: FragmentManager): RetainFragment {
            return (fm.findFragmentByTag(TAG) as? RetainFragment) ?: run {
                RetainFragment().also {
                    fm.beginTransaction().add(it, TAG).commit()
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true
    }
}

Java

private LruCache<String, Bitmap> memoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    memoryCache = retainFragment.retainedCache;
    if (memoryCache == null) {
        memoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.retainedCache = memoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> retainedCache;

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
            fm.beginTransaction().add(fragment, TAG).commit();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

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