Caching bitmaps [cache bitmap]
- Loading a single bitmap to the UI is simple and straightforward. However, if you need to load a large number of images at a time, it will become complicated. In most cases (for example
ListView
,GridView
OrViewPager
),
There is usually no limit on the number of images displayed.
- The sub-view can be used cyclically to suppress memory usage. GC (Garbage Collector) also releases bitmaps that are no longer needed. These mechanisms are very good, but to maintain a smooth user experience, you want to avoid repeated processing of those images each time when the screen slides back. Memory and disk cache can usually help, allowing components to quickly reload those processed images.
- This section describes how to use memory cache and disk cache when loading multiple bitmaps to improve the response speed and UI smoothness.
Use a memory cache [use memory cache]
- The memory cache allows you to access bitmap quickly on the premise of spending valuable program memory.
LruCache
Class (in support
It can also be found in the library) is particularly suitable for caching bitmaps, with a strong referencedLinkedHashMap
To save recently referenced objects, and when the cache size exceeds the set size, the least recently used objects (evict) are kicked out.
- Note: In the past, a popular memory cache implementation method was to use
SoftReference
OrWeakReference
,
However, this is not recommended..
- Since Android 2.3 (API level 9), GC has become more frequent to release soft/weak references, which makes them appear less efficient [easy to GC and continuous creation]. And in Android
Before 3.0 (API level 11), the backup bitmap was stored in native memory, and it was not released in a predictable manner, which may cause the program to crash when it exceeds its memory limit.
- To select an appropriate size for lrucache, consider the following factors:
- How much memory does your program have?
- How many images are displayed on the screen at a time? How many images need to be ready for immediate display to the screen?
- What is the screen size and density of a device? A device with a particularly high-density screen (xhdpi), like Galaxy Nexus
S (hdpi) needs a larger cache to hold the same number of images.
- What is the size and configuration of bitmap, and how much memory will be consumed?
- How often are images accessed? Are some of them more frequently accessed than others? If yes, you may want to save those most frequently accessed to the memory, or set multiple bitmaps (grouped by Access Frequency) for different groups.
LruCache
Object.
- Can you balance quality and quantity? Sometimes it is very useful to save a large number of low-quality bitmaps and load high-quality images in another background task.
- No specified size and formula can be applied to all programs. It depends on analyzing your usage and proposing a suitable solution. A cache that is too small will lead to additional costs, but there is no obvious benefit. A cache that is too large will also lead
Java. Lang. outofmemory exception [cache occupies too much memory, and other activities will be abnormal due to insufficient memory], and your program leaves only a small amount of memory for work.
- Below is a bitmap Creation
Lrucache example:
private LruCache mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) { ... // Get memory class of this device, exceeding this amount will throw an // OutOfMemory exception. final int memClass = ((ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE)).getMemoryClass(); // Use 1/8th of the available memory for this memory cache. final int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new LruCache(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number of items. return bitmap.getByteCount(); } }; ...}public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); }}public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key);}
- Note: In the above example, 1/8 of the program memory is used as the cache. On a common device (hdpi), the minimum size is about 4 MB.
(32/8). A full screenGridView
If the image is filled with 1.5x800 pixels, it will take about 480 MB (2.5*4 bytes). Therefore, at least images can be cached in the memory.
- When a bitmap is loaded
ImageView
,LruCache
The system will first check whether the image exists. If yes, it will be used to update immediatelyImageView
Component. Otherwise, a background thread is triggered to process the image.
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 in the above program
You also need to add the operation to the memory cache:
class BitmapWorkerTask extends AsyncTask { ... // 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; } ...}
Use a disk cache [use disk cache]
- The memory cache can improve the access to the recently viewed bitmap, but you cannot guarantee that the image will be in the cache. Similar
GridView
Components with a large amount of data can easily fill up the memory cache. Your program may be like phone
Call and other tasks are interrupted, so that the background program may be killed, and the memory cache will be destroyed. Once the user restores the previous status, your program needs to re-process each image.
- Disk cache the disk cache can be used to save processed bitmaps and reduce the number of times the images are loaded when they are unavailable in the memory cache. Of course, reading images from a disk is slower than reading images from the memory, and the reading operation must be processed in the background thread, because the disk reading operation is unpredictable.
- Note:If images are accessed more frequently, you may use
ContentProvider
Will be more appropriate, such as in the Gallery program.
- The following sample code implements a basic
DiskLruCache
. However, Android
4.0 of the source code provides a more robust and is recommended for useDiskLruCache
Solution. (libcore/luni/src/main/java/libcore/io/DiskLruCache.java
).
Because it is backward compatible, it can also be used directly in the previous Android version. (Quick search provides an example to implement this solution ).
private DiskLruCache mDiskCache;private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails";@Overrideprotected void onCreate(Bundle savedInstanceState) { ... // Initialize memory cache ... File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); ...}class BitmapWorkerTask extends AsyncTask { ... // 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(String.valueOf(imageKey, bitmap); return bitmap; } ...}public void addBitmapToCache(String key, Bitmap bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } // Also add to disk cache if (!mDiskCache.containsKey(key)) { mDiskCache.put(key, bitmap); }}public Bitmap getBitmapFromDiskCache(String key) { return mDiskCache.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.public static File getCacheDir(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.getExternalStorageState() == Environment.MEDIA_MOUNTED || !Environment.isExternalStorageRemovable() ? context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName);}
- The memory cache check can be performed in the UI thread, and the disk cache check must be processed in the background thread. Disk operations should never occur in the UI thread. After the image processing is complete, the final bitmap needs to be added to the memory cache and disk cache for later use.
Handle configuration changes [Handle configuration changes]
- During running, the configuration changes. For example, changing the screen direction will cause android to destory and restart the currently running activity. (For more information about this behavior, see handling runtime changes ). you want to avoid re-processing all the images when the configuration changes, so as to provide users with a smooth and excessive experience.
- Fortunately, you already know how to create a memory cache in the use a memory cache section. This cache can be called by using a fragment
setRetainInstance(true)
Passed to the new activity. After this activity is recreate,
This reservedFragment
Will be re-attached to the white. In this way, you can access the cache object, obtain the image information from it, and quickly add it to the imageview object.
- Use fragment to retrieve the following configuration changes
LruCache
Example:
private LruCache mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) { ... RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = RetainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache(cacheSize) { ... // Initialize cache here as usual } mRetainFragment.mRetainedCache = mMemoryCache; } ...}class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache mRetainedCache; public RetainFragment() {} public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); }}
- To test the above effect, try to compare the retaining
Fragment
. Rotate the screen when this is not done. You will find that there is almost no card redrawing from the memory cache, while the disk cache is a little slow. If neither of the two caches is available, the processing speed is the same as usual.
Learn from: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html. please give me more instructions. Thank you!
For more information, see http://blog.csdn.net/kesenhoo. Thank you for your cooperation!