Android cache bitmaps

Source: Internet
Author: User

It is very easy to load a bitmap (Bitmap) to your UI in Android, but if you want to load a large number at a time, it will become much more complicated. In most cases (components such as listview, gridview, or viewpager), the number of images on the screen and the total number of images that will be immediately rolled to the screen, in essence, it is unrestricted.

Components like this will recycle the view after the sub-view is removed from the screen, and the memory usage will be retained. However, if you do not retain any reference for long-term survival, the garbage collector will release the bitmap you load. This is a good choice, but in order to maintain a smooth and fast loading UI, you must avoid re-processing when the image is returned to the screen. Memory and hard disk cache can solve this problem, and cache allows components to quickly load and process images.

This lesson will show you how to use memory and hard disk cache bitmap to improve UI responsiveness and smoothness when loading multiple bitmaps.

Use memory cache

At the cost of valuable application memory, the memory cache provides a fast bitmap access method. The lrucache class (which can be obtained in the support library and supported by API level 4 or later, or version 1.6 or later) is very suitable for caching bitmap tasks, it stores recently referenced objects in a strongly referenced linkedhashmap and releases infrequently used objects after the cache size exceeds the specified size.

Note: In the past, a very popular memory cache implementation was softreference (soft reference) or weakreference (weak reference) bitmap cache solution. However, it is not recommended now. Since android2.3 (API level 9), the garbage collector focuses more on the collection of Soft/weak references, which makes the above solution quite ineffective. In addition, Android
In versions earlier than 3.0 (API level 11), the backup data of bitmap is directly stored in the local memory and released from the memory in an unpredictable way, it is very likely that the program will crash if it exceeds the memory limit temporarily.

To select a proper size for lrucache, we need to consider many reasons, such:

• Are other activities and/or programs very memory-consuming?

• How many images will be displayed on the screen? How many images will be displayed on the screen?

• What is the screen size and density of a device? A device with an ultra-HD screen (xhdpi), such as Galaxy Nexus, needs a larger cache space to cache the same number of images than Nexus S (hdpi.

• How much memory does bitmap size, configuration, and each image occupy?

• Are images frequently accessed? Will some be accessed more frequently than others? If so, you may need to keep some images in the memory, or even allocate multiple lrucache objects to different groups of bitmaps.

• Can you balance the quality and quantity of images? Sometimes it is more useful to store a large number of low-quality images, and then you can load images of another high-quality version in the background task.

There is no specification for setting the cache size for all applications. It depends on the appropriate solution provided after memory usage analysis. If the cache space is too small, there is no benefit, but it will lead to additional overhead. If it is too large, it may cause java. Lang. outofmemory exceptions again or leave only a small space for other applications to run.

Here is an lrucache example for setting bitmap:

Private lrucache <string, bitmap> mmemorycache;

@ Override
Protected void oncreate (bundle savedinstancestate ){
...
// Get memory class of this device, exceeding this amount will throw
// 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 <string, bitmap> (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 this example, 1/8 of the application memory is allocated to the cache. The minimum size of a common/hdpi device is about 4 MB (32/8 ). On a device with a resolution of 800*480, the full screen filled image's gridview occupies about 1.5 MB of memory (800*480*4 bytes ), therefore, the memory size can cache images of around 2.5 pages.

When loading a bitmap to imageview, check lrucache first. If the corresponding data exists, it is immediately used to update the imageview. Otherwise, the background thread will be started to process the image.

Public void loadbitmap (INT resid, 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.exe cute (resid );
}
}

Bitmapworkertask also needs to update data in the memory:

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;
}
...
}

Use hard disk cache

A memory cache is very helpful for accelerating access to the recently browsed bitmap, but you cannot be limited to available images in the memory. Components with larger datasets such as gridview can easily consume the memory cache. Your application may be interrupted when executing other tasks (such as making a phone call), and tasks in the background may be killed or the cache may be released. Once the user resume to your application, you have to process each image again.

In this case, the hard disk cache can be used to store bitmap and reduce the image loading time (times) after the image is released by the memory cache ). Of course, loading images from a hard disk is slower than the memory and should be performed in the background thread, because the hard disk reading time is unpredictable.

Note: If you frequently access images, contentprovider may be more suitable for storing cached images, such as image gallery applications.

The sample code in this class is implemented using disklrucache (from the android source code. In the sample code, in addition to the existing memory cache, the hard disk cache is also added.

Private disklrucache mdisklrucache;
Private final object mdiskcachelock = new object ();
Private Boolean mdiskcachestarting = true;
Private Static final int disk_cache_size = 1024*1024*10; // 10 MB
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(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; // finished Initialization
Mdiskcachelock. policyall (); // 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 ){
Mmemorycache. Put (Key, bitmap );
}

// Also add to disk cache
Synchronized (mdiskcachelock ){
If (mdisklrucache! = NULL & mdisklrucache. Get (key) = NULL ){
Mdisklrucache. Put (Key, bitmap );
}
}
}

Public bitmap getbitmapfromdiskcache (string key ){
Synchronized (mdiskcachelock ){
// Wait while disk cache is started from background thread
While (mdiskcachestarting ){
Try {
Mdiskcachelock. Wait ();
} Catch (interruptedexception e ){}
}
If (mdisklrucache! = NULL ){
Return mdisklrucache. 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, 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 );
}

Note: hard disk operations are required even for hard disk cache initialization, so they should not be performed in the main thread. However, this means that the hard disk cache can be accessed before initialization. To solve this problem, a lock object is added in the above implementation to ensure that the application cannot access the hard disk cache before the cache is initialized.

Check the memory cache in the UI thread. Check the hard disk cache in the background thread. Hard Disk operations are never performed in the UI thread. After the image processing is complete, the final bitmap will be added to the memory cache and hard disk cache for future use.

Process configuration changes

The configuration during running will change. For example, changing the screen direction will cause android to destroy and restart the activity with a new configuration (for more information about this problem, see handling runtime changes ). To ensure a smooth and Fast User Experience, you need to avoid re-processing all the images when the configuration changes.

Fortunately, you have constructed a good memory cache for bitmap in the "use memory cache" section. These memories can be passed to the new activity instance by using fragment, which can be retaininstance (true) by calling the setretaininstance method. After the activity is re-created, you can access an existing cache object in the fragment above, so that the image can be quickly loaded and repopulated into the imageview object.

The following is an example of retaining the lrucache object in configuration changes using fragment:

Private lrucache <string, bitmap> mmemorycache;

@ Override
Protected void oncreate (bundle savedinstancestate ){
...
Retainfragment mretainfragment =
Retainfragment. findorcreateretainfragment (getfragmentmanager ());
Mmemorycache = retainfragment. mretainedcache;
If (mmemorycache = NULL ){
Mmemorycache = new lrucache <string, bitmap> (cachesize ){
... // Initialize cache here as usual
}
Mretainfragment. mretainedcache = mmemorycache;
}
...
}

Class retainfragment extends fragment {
Private Static final string tag = "retainfragment ";
Public lrucache <string, bitmap> 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 this, you can rotate the device screen without applying fragment. When the cache is retained, you should be able to find that filling images into the activity is almost instantly taken from the memory without any delay. Any image is obtained from the memory cache first. If no image is found from the hard disk cache, the image will be loaded in normal mode.

Related Article

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.