Android Official Development Document Training Series Course Chinese version: Bitmap caching for efficient display of bitmaps

Source: Internet
Author: User

Original address: http://android.xsoftlab.net/training/displaying-bitmaps/cache-bitmap.html

The process of loading a single picture into the UI interface is simple, but if you need to load a lot of pictures at a time, it's a little bit more complicated. In many cases, such as using a ListView, GridView, or Viewpager to display a certain number of images, in essence these cases, a quick swipe of the screen causes a large number of images to be displayed on the screen.

A component like this that removes a child view that is removed to the screen by recycling suppresses the use of memory (that is, they do not abuse memory themselves). The garbage collector also frees the bitmap you loaded, assuming you're not using any persistent references. This is great, but in order to maintain a smooth UI effect, you may need to re-process them in a normal way every time they return to the screen. Memory caches and disk caches can be helpful here, allowing these components to quickly reload images that have already been processed.

This lesson will discuss how to make the UI interface smoother and more responsive by using memory caching and disk caching when loading multiple images.

Use memory Cache

The memory cache provides a quick way to access bitmaps, but this can cost valuable memory space. Class LRUCache is ideal for working with cached pictures, it stores references to recently used bitmaps on a Linkedhashmap object, and it expels the last unused member before it exceeds the memory design size.

Note: in the past, caching images using SoftReference or WeakReference is the most popular way to cache, but it is not recommended. After Android 2.3, the garbage collector is more forced to recycle soft/weak references, which makes these references virtually ineffective. In addition, before Android 3.0, the bitmap's byte data is stored in local memory, it can be foreseen that the data will not be released, which will cause the program to easily exceed its own memory limit, and then crash.

In order to choose the right size for the LRUCache, several factors should be considered within:

    • What is the amount of memory used by the activity or program in the normal state?
    • How many pictures will be displayed on the screen at the same time? How much memory is needed to prepare the image to be displayed on the screen?
    • What is the size and size of the device screen? In the case of loading the same number of images, the ultra-high density (xhdpi) device like the Galaxy Nexus requires much more memory than the Nexus S (HDPI).
    • What is the size of the picture? What is the configuration? What is the amount of memory required to load this bitmap?
    • How often are images accessed? Will it be more frequent than other bitmaps? If so, you might need to keep them in memory forever, or even have multiple LRUCache objects to group the pictures.
    • Can you strike a balance between quantity and quality? Sometimes it's useful to store a lot of low-quality pictures, potentially there are some background tasks to load some high-quality versions.

There is no specific size or configuration, but this applies to all applications, depending on the analysis of memory usage and the need to find a suitable solution. The cache setting is too small to cause insignificant extra overhead, the cache setting is too general to cause the java.lang.OutOfMemory exception again, and the size should be set to the amount of memory that is remaining outside the regular memory usage of the app.

Here is an example of using a LRUCache cache bitmap:

PrivateLrucache<string, bitmap> Mmemorycache;@Overrideprotected void onCreate(Bundle savedinstancestate) {    ...//Get Max available VM memory, exceeding this amount would throw an    //OutOfMemory exception. Stored in kilobytes as LruCache takes an    //int in its constructor.    Final intMaxMemory = (int) (Runtime.getruntime (). MaxMemory ()/1024x768);//Use 1/8th of the available memory for this memory cache.    Final intCacheSize = maxmemory/8; Mmemorycache =NewLrucache<string, bitmap> (cacheSize) {@Override        protected int sizeOf(String key, Bitmap Bitmap) {//The cache size is measured in kilobytes rather than            //number of items.            returnBitmap.getbytecount ()/1024x768;    }    }; ...} Public void Addbitmaptomemorycache(String key, Bitmap Bitmap) {if(Getbitmapfrommemcache (key) = =NULL) {Mmemorycache.put (key, bitmap); }} PublicBitmapGetbitmapfrommemcache(String key) {returnMmemorycache.get (key);}

Note: in this example, one-eighth of the memory is allocated to the cache. On the normal device (HDPI) This is about 4MB (32/8). A GridView full of pictures occupies approximately 1.5MB (800*480*4 bytes) of memory on 800*480 devices in full-screen mode, so this can store approximately 2.5 pages of images in memory.

When loading a bitmap onto the ImageView, first check the LRUCache. If a match is found, it will be used to update the ImageView immediately, or a background thread will be triggered to process the picture:

publicvoidloadBitmap(int resId, ImageView imageView) {    final String imageKey = String.valueOf(resId);    final Bitmap bitmap = getBitmapFromMemCache(imageKey);    ifnull) {        mImageView.setImageBitmap(bitmap);    else {        mImageView.setImageResource(R.drawable.image_placeholder);        new BitmapWorkerTask(mImageView);        task.execute(resId);    }}

The memory cache also needs to be added or updated in Bitmapworkertask:

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

The memory cache is useful for fast loading of recently browsed images, but it is not possible to store all the images in the memory cache. Components such as the GridView can easily fill the memory cache when loading large data sets. The program may be interrupted by other tasks during operation, such as a call, when the task in the background may be killed and the memory cache will be destroyed. Once the user returns to the interface, the program needs to re-process each image again.

The disk cache is helpful in these cases, it can store processed pictures, and it will help to increase the load time of the picture, when the picture no longer exists in the memory cache. Of course, getting a picture on disk is slower than in memory, and you need to open a separate worker thread, which is not as predictable as the time it takes to read data from disk.

Note: ContentProvider may be more suitable for storing cached images if they are accessed more frequently, just like in a photo album application.

The sample code updated from Android source uses a Disklrucache implementation. The following is an updated version that adds a disk cache to an existing memory cache:

PrivateDisklrucache Mdisklrucache;Private FinalObject Mdiskcachelock =NewObject ();Private BooleanMdiskcachestarting =true;Private Static Final intDisk_cache_size =1024x768*1024x768*Ten;//10MBPrivate Static FinalString Disk_cache_subdir ="Thumbnails";@Overrideprotected void onCreate(Bundle savedinstancestate) {    ...//Initialize Memory Cache...//Initialize disk cache on background threadFile Cachedir = Getdiskcachedir ( This, Disk_cache_subdir);NewInitdiskcachetask (). Execute (CACHEDIR); ...} Class Initdiskcachetask extends Asynctask<file, Void, void> {@Override    protectedVoidDoinbackground(File ... params) {synchronized(Mdiskcachelock) {File Cachedir = params[0];            Mdisklrucache = Disklrucache.open (Cachedir, disk_cache_size); Mdiskcachestarting =false;//Finished initializationMdiskcachelock.notifyall ();//Wake any waiting threads}return NULL; }}class Bitmapworkertask extends Asynctask<integer, Void, bitmap> {...//Decode image in background.    @Override    protectedBitmapDoinbackground(Integer ... params) {FinalString ImageKey = string.valueof (params[0]);//Check disk cache in background threadBitmap Bitmap = Getbitmapfromdiskcache (ImageKey);if(Bitmap = =NULL) {//not found in disk cache            //Process as normal            FinalBitmap Bitmap = Decodesampledbitmapfromresource (Getresources (), params[0], -, -)); }//ADD final bitmap to cachesAddbitmaptocache (ImageKey, bitmap);returnBitmap }    ...} 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    synchronized(Mdiskcachelock) {if(Mdisklrucache! =NULL&& mdisklrucache.get (key) = =NULL) {Mdisklrucache.put (key, bitmap); }    }} PublicBitmapGetbitmapfromdiskcache(String key) {synchronized(Mdiskcachelock) {//Wait while disk cache was started from background thread         while(mdiskcachestarting) {Try{mdiskcachelock.wait (); }Catch(Interruptedexception e) {}        }if(Mdisklrucache! =NULL) {returnMdisklrucache.get (key); }    }return NULL;}//Creates a unique subdirectory of the designated app cache directory. Tries to use externalmounted, falls back on internal storage. Public StaticFileGetdiskcachedir(context context, String uniqueName) {//Check If media is mounted or storage are built-in, if so, try and use external cache dir    //Otherwise use internal cache dir    FinalString CachePath = Environment.MEDIA_MOUNTED.equals (Environment.getexternalstoragestate ()) | | !isexternalstorageremovable ()? Getexternalcachedir (context). GetPath (): Context.getcachedir (). GetPath ();return NewFile (CachePath + file.separator + uniqueName);}

Note: because disk cache initialization requires disk operations, this process should not be performed in the UI thread. However, this also means that this is an opportunity to access before the cache is initialized. To do this, a lock object is required to ensure that the app does not read data from the disk cache until the cache is initialized.

The memory cache performs checks in the UI thread, and the disk cache performs checks in the background thread. Disk operations should never be placed in the UI thread. When the image is processed, the final processed image should be added to the memory cache and the disk cache for backup.

Handling Configuration changes

If a change occurs at run time, such as a change in the orientation of the screen, which causes Android to destroy and restart the activity in operation, you may want to avoid processing the image again, so that once the configuration has changed, you can have a smooth and fast user experience.

Fortunately, you have a very good memory caching scheme: You can use fragment with Setretaininstance (true) set to pass the cache to the new activity instance. At the time of activity re-creation, this retained fragment will be reattached to the activity and you can gain access to the original memory cache, which allows the image to be quickly obtained and re-populated in the ImageView object.

The following example uses the fragment that references LRUCache and passes the issue of configuration changes:

PrivateLrucache<string, bitmap> Mmemorycache;@Overrideprotected void onCreate(Bundle savedinstancestate)    {    ...    Retainfragment retainfragment = retainfragment.findorcreateretainfragment (Getfragmentmanager ()); Mmemorycache = Retainfragment.mretainedcache;if(Mmemorycache = =NULL) {Mmemorycache =NewLrucache<string, bitmap> (cacheSize) {...//Initialize cache here as usual} Retainfragment.mretainedcache = Mmemorycache; }    ...} Class Retainfragment extends Fragment {Private Static FinalString TAG ="Retainfragment"; PublicLrucache<string, bitmap> Mretainedcache; Public retainfragment() {} Public StaticRetainfragmentfindorcreateretainfragment(Fragmentmanager FM) {Retainfragment fragment = (retainfragment) fm.findfragmentbytag (TAG);if(Fragment = =NULL) {fragment =NewRetainfragment ();        Fm.begintransaction (). Add (fragment, TAG). commit (); }returnFragment }@Override     Public void onCreate(Bundle savedinstancestate) {Super. OnCreate (Savedinstancestate); Setretaininstance (true); }}

To test this output, try rotating the device with and without fragment. You should notice that there is little delay in the process. If any image is not found in the memory cache, then this provides the disk cache with the application, if not, then the conventional processing method will appear.

Android Official Development Document Training Series Course Chinese version: Bitmap caching for efficient display of bitmaps

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.