If you only need to load an image, you can directly load it. however, if you want to load a large number of images in controls like ListView, GridView, or ViewPager, the problem becomes complicated. when using such controls, the number of images that may be displayed on the screen in a short period of time is not fixed.
This type of control maintains a low memory usage by reusing the child View. garbage Collector also releases the corresponding Bitmap when the View is reused to ensure that these useless bitmaps do not exist in the memory for a long time. however, to ensure smooth sliding of controls, when a View slides on the screen again, we need to avoid repeated image loading. at this time, opening up a cache space on the memory and disk often ensures fast duplicate loading of images.
Use memory cache
A memory cache can make it possible to quickly load images based on a certain amount of application memory.LruCache is used to cache images and save recently used objects inRelease objects that have not been used recently in LinkedHashMap.
To determine a proper LrcCache size, consider the following factors:
1. memory usage of other components in the Application
2. How many images may be displayed on the screen? How many images will be displayed on the screen?
3. What is the size and density of the screen? Compared with high screen density devices such as Nexus S, devices with high screen density such as Galaxy Nexs often require larger cache space to store the same number of images.
4. image size and other parameters, as well as the memory size of each image.
5. How often are images accessed? Are some images more frequently accessed than others? In this case, we may need to keep some images in the memory, or use multiple LrcCache to group different bitmaps.
6. We also need to weigh the number and quality of images. Sometimes, a large number of thumbnails are stored in the cache, while loading HD images in the background will significantly improve the efficiency.
For each application, the cache size that needs to be specified is not certain, which depends on our analysis of the application and the corresponding solution. if the cache space is too small, it may cause additional overhead, which does not make up for the whole application. If the cache space is too large, it may cause java. lang. outOfMemory is abnormal and the memory space reserved for other components is also reduced.
The following is an example of initializing LrcCache for storing Bitmap:
Private LruCache <String, Bitmap> mMemoryCache; @ Overrideprotected void onCreate (Bundle savedInstanceState ){... // obtain the maximum available space. If the required space exceeds this size, an OutOfMemory exception is thrown. // The final int maxMemory = (int) parameter in the LrcCache constructor is measured in kilobytes) (Runtime. getRuntime (). maxMemory ()/1024); // The cache size here is 1/8 final int cacheSize = maxMemory/8; mMemoryCache = 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. // The cache size is measured in kilobytes to return bitmap. getByteCount ()/1024 ;}};...} // save Bitmap to the cache public void addBitmapToMemoryCache (String key, Bitmap bitmap) {if (getBitmapFromMemCache (key) = null) {// when using the (getBitmapFromMemCache method, obtain Bitmap Based on the input key // when the obtained Bitmap is empty, it proves that the Bitmap has not been stored. // at this time, the Bitmap is stored in the LrcCache mMemoryCache. put (key, bitmap) ;}// obtain the corresponding Bitmappublic Bitmap getBitmapFromMemCache (String key) {return mMemoryCache from LrcCache based on the key. get (key );}
Note:In this example, 1/8 of the application memory is allocated as a cache. on a device with normal/high screen resolution, the cache size is around 4 MB (32/8 MB ). however, if you use a 800x480 resolution image to fill in a full-screen GridView, it requires 800 mb of memory (480*4 bytes ), therefore, this cache can store at least 2.5 pages of images.
When an image is loaded to the ImageView, LrcCache first checks whether the image exists. if an image exists, the image is immediately updated to the ImageView. Otherwise, a background thread is enabled to load the image.
Public void loadBitmap (int resId, ImageView imageView) {// convert the image resource id to String type, as key final String imageKey = String. valueOf (resId); // obtain Bitmap final Bitmap bitmap = getBitmapFromMemCache (imageKey) from LruCache based on the key; if (bitmap! = Null) {// If the obtained Bitmap is not empty, // update the obtained Bitmap to mImageView in ImageView. setImageBitmap (bitmap);} else {// otherwise, set a bitmap mImageView in ImageView. setImageResource (R. drawable. image_placeholder); // enable a new asynchronous task to load the image BitmapWorkerTask task = new BitmapWorkerTask (mImageView); task.exe cute (resId );}}
BitmapWorkerTask also needs to be updated. The Bitmap is stored in LrcCache as a key-value pair.
Class BitmapWorkerTask extends AsyncTask <Integer, Void, Bitmap> {... // load the image @ Override protected Bitmap doInBackground (Integer... params) {final Bitmap bitmap = decodeSampledBitmapFromResource (getResources (), params [0], 100,100 )); // store the Bitmap object as a key-Value Pair in LrcCache addBitmapToMemoryCache (String. valueOf (params [0]), bitmap); return bitmap ;}...}
Use disk cache
The memory cache can greatly improve the efficiency of accessing recently used images, but we cannot expect that all the images needed can be found in the memory cache. controls that have a large amount of data in data sources such as GridView can easily occupy the memory cache. however, our application may be interrupted by other tasks (switch to the background), such as answering the phone number. When our application is switched to the background, it is very likely to be closed, the memory cache will also be destroyed. when the user returns our application, the application needs to reload the required image.
The disk cache will continue to load images when the memory cache is destroyed, so that when the memory cache is unavailable but the image needs to be loaded, the loading time can be reduced. of course, reading images from a disk is slower than reading images from the memory, and must be executed in the background thread because the image loading time is not certain.
Note:: If cached images need to be accessed frequently, it is better to store these cached images to ContentProvider. This is what the example library application does.
The following example shows an implementation of DiskLruCache (Android source). In this example, the disk cache is added based on the memory cache.
Private DiskLruCache mDiskLruCache; private final Object mDiskCacheLock = new Object (); private boolean mDiskCacheStarting = true; private static final int DISK_CACHE_SIZE = 1024*1024*10; // 10 MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails"; @ Overrideprotected void onCreate (Bundle savedInstanceState ){... // initialize the memory cache... // initialize the disk cache File cacheDir = getDiskCacheDir (this, DISK_CACHE _ SUBDIR); new initdiskcachetask(cmd.exe cute (cacheDir );...} class InitDiskCacheTask extends AsyncTask <File, Void, Void >{@ Override protected Void doInBackground (File... params) {synchronized (mDiskCacheLock) {File cacheDir = params [0]; mDiskLruCache = DiskLruCache. open (cacheDir, DISK_CACHE_SIZE); mDiskCacheStarting = false; // identify the end of mDiskCacheLock initialization. yyall (); // wake up the thread in the waiting state} return null;} class BitmapWorkerTask extends AsyncTask <Integer, Void, Bitmap> {... // parse the image @ Override protected Bitmap doInBackground (Integer... params) {final String imageKey = String. valueOf (params [0]); // checks whether the image already exists in the disk cache in the background thread. Bitmap bitmap = getBitmapFromDiskCache (imageKey); if (bitmap = null) {// does not exist in the disk cache. // The final Bitmap bitmap = decodeSampledBitmapFromResource (getResources (), params [0], 100, 10 0);} // Add the loaded image to the cache. addBitmapToCache (imageKey, bitmap); return bitmap ;}...} public void addBitmapToCache (String key, Bitmap bitmap) {// Add the image to the memory cache if (getBitmapFromMemCache (key) = null) {mMemoryCache. put (key, bitmap);} // Add the image to the disk cache at the same time. synchronized (mDiskCacheLock) {if (mDiskLruCache! = Null & mDiskLruCache. get (key) = null) {mDiskLruCache. put (key, bitmap) ;}} public Bitmap getBitmapFromDiskCache (String key) {synchronized (mDiskCacheLock) {// wait for while (mDiskCacheStarting) when the disk cache is being initialized) {try {mDiskCacheLock. wait () ;}catch (InterruptedException e) {}} if (mDiskLruCache! = Null) {return mDiskLruCache. get (key) ;}} return null ;}// when external storage is available, create a unique sub-folder in the specified application folder as the cache directory // when the external device is unavailable, use the built-in storage public static File getDiskCacheDir (Context context, String uniqueName) {// check whether the external storage is available. If available, use the cache directory of the external storage. // otherwise, use the cache directory of the internal storage. final String cachePath = Environment. MEDIA_MOUNTED.equals (Environment. getExternalStorageState () |! IsExternalStorageRemovable ()? GetExternalCacheDir (context). getPath (): context. getCacheDir (). getPath (); return new File (cachePath + File. separator + uniqueName );}
Note:Since disk cache operations involve disk operations, all the procedures here cannot be executed in the UI thread. at the same time, this means that the disk cache can be accessed before Initialization is complete. to solve this problem, a lock is added to the above method, which ensures that the disk cache will not be read by the application before initialization.
Although the check of the memory cache can be performed in the UI thread, the disk cache inspection must be performed in the background thread. disk design operations should not be performed in the UI thread in any way. when the image is loaded successfully, the obtained image will be added to the two caches for use.
Process configuration changes
When running, the configuration changes, for example, the screen direction. this change will destroy the Android system and use the new configuration to recreate the currently executed Activity (for more information, see Handling Runtime Changes ). to ensure a smooth user experience, we need to avoid reloading all images.
Fortunately, we have a good memory cache, which can be calledThe setRetainInstance (true) method of Fragment is saved and passed to the new Activity. after the Activity is re-built, the Fragment can be re-attached to the new Activity, so that we can use the existing memory cache to quickly retrieve images and display them in ImageView.
The following code uses Fragment to retain LruCache:
Private LruCache <String, Bitmap> mMemoryCache; @ Overrideprotected void onCreate (Bundle savedInstanceState ){... // obtain the RetainFragment retainFragment RetainFragment used to save the LruCache. findOrCreateRetainFragment (getFragmentManager (); // retrieves the Fragment LruCache mMemoryCache = retainFragment. mRetainedCache; if (mMemoryCache = null) {// if LruCache is empty, no cache is available. // you need to create and initialize an LruCache mMemoryCache = new LruCache <String, bitmap> (cacheSize ){... // Initialize cache here as usual} // store the new LruCache in Fragment retainFragment. mRetainedCache = mMemoryCache ;}...} class RetainFragment extends Fragment {private static final String TAG = "RetainFragment"; public LruCache <String, Bitmap> mRetainedCache; public RetainFragment () {} // create or obtain the Fragment public static RetainFragment listener (FragmentManager fm) for storing LruCache from FragmentManager. {// obtain the corresponding Fragment RetainFragment fragment = (Fragment) based on the tag) fm. findFragmentByTag (TAG); if (fragment = null) {// if Fragment is empty, if no Fragment is available, it indicates that no LruCache is available. // you need to create a new Fragment to store LruCache fragment = new RetainFragment (); // Add the Fragment to FragmentManager and fm. beginTransaction (). add (fragment, TAG ). commit () ;}return fragment ;}@ Override public void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); // sets the Fragment to be reattached to the Activity when the Activity is rebuilt. setRetainInstance (true );}}
To verify the effect (whether to re-attach the Fragment to the Activity), we can rotate the screen. you will find that there is almost no latency when we save the memory cache through Fragment, re-Fetch the image after recreating the Activity. images that do not exist in the memory cache may be stored in the disk cache. If the disk cache does not exist, the required images will be loaded normally.