This article describes how to use cache to improve the fluency of loading input and sliding of UI. Using memory cache, using disk cache, handling configuration change events and other methods will effectively solve this problem.
It is very simple to display a single image in your UI, and it is a bit complicated if you need to display a lot of images at once. In many cases (such as using ListView, GridView or ViewPager controls), the number of images displayed on the screen and the images to be displayed on the screen is very large (such as browsing a large number of images in the gallery).
In these controls, when a child control is not displayed, the system will reuse the control to cycle through the display to reduce memory consumption. At the same time, the garbage collection mechanism will also release Bitmap resources that have been loaded into memory (assuming you do not strongly reference these Bitmap). Generally speaking, this is good, but when the user slides the screen back and forth, in order to ensure the smoothness of the UI and the efficiency of loading pictures, you need to avoid repeated processing of these pictures that need to be displayed. This problem can be solved by using memory cache and disk cache. Using cache allows controls to quickly load processed images.
This article describes how to use cache to improve the fluency of loading input and sliding of UI.
Using memory cache
Memory cache increases the speed of accessing pictures, but it takes up a lot of memory. LruCache
Classes (before API 4, you can use classes in Support Library) are particularly suitable for caching Bitmap, and use the most recently used ones.
Bitmap object is saved with strong reference (save to LinkedHashMap). When the cache number reaches the predetermined value,
Object deletion that is not used frequently.
Notice:In the past, the common practice of implementing memory caching was to use
SoftReference or
WeakReference bitmap cache,
However, this method is not recommended. Starting from Android 2.3 (API Level 9), garbage collection begins to force the soft/weak reference to be recycled, resulting in no efficiency improvement in these caches.
In addition, before Android 3.0 (API Level 11), these cached Bitmap data were stored in the underlying memory, and these objects were not released after the predetermined conditions were met, which may cause
The program exceeds the memory limit and crashes.
When using LruCache, you need to consider the following factors to choose an appropriate cache quantity parameter:
1. How much memory is available in the program
2. How many pictures are displayed on the screen at the same time? How many images should I cache first to display on the screen I'm about to see?
3. What is the screen size and screen density of the device? Super high screen density (xhdpi, for example, Galaxy Nexus)
4. The device displays the same picture and requires more memory than a low-screen density (hdpi, for example, Nexus S) device.
5. The size and format of the picture determine how much memory each picture needs to occupy
6. How frequently are the image access? Is some pictures accessing much more frequently than others? If so, you may need to put these frequently accessed images into memory.
7. How to balance quality and quantity? It is very useful to save a large number of low-quality images in some cases, and use background threads to add a high-quality version of the image when needed.
There is no universal recipe here that fits all programs, you need to analyze your usage and specify your own cache strategy. Using too small cache will not have the effect as it should be, and using too large cache will consume more
The memory may cause exceptions or leave little memory for other functions of your program.
Here is an example using LruCache cache:
private LruCache<string, bitmap=""> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) (
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 ();
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return (key);
}
Notice:In this example, 1/8 of the program's memory is used for cache. In a normal/hdpi device, this has at least 4MB (32/8) memory.
In a device with a resolution of 800×480, the full screen GridView will use approximately 1.5MB (800*480*4 bytes)
, so almost 2.5 pages of pictures are cached in memory.
When displaying the image in the ImageView,
First check whether there is any in LruCache. If it exists, use the cached image. If it does not exist, start the background thread to load the image and cache it:
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = (resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
(bitmap);
} else {
(.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
(resId);
}
}
BitmapWorkerTask needs to add new images to the cache:
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((params[0]), bitmap);
return bitmap;
}
...
}
The next page will introduce you to two other methods of using disk caching and handling configuration change events.
Using disk cache
Memory caches are fast when accessing recently used pictures, but you can't determine if the picture exists in the cache. picture
GridView controls may have many images to display, and the image data will soon fill the cache capacity.
At the same time, your program may be interrupted by other tasks, such as calls made - when your program is in the background, the system may clearly see these image caches. Once the user resumes using your program, you will also need to reprocess these images.
In this case, the disk cache can be used to save these processed images, and when these images are unavailable in the memory cache, they can be loaded from the disk cache to omit the image processing process.
Of course, loading images from disk is much slower than reading from memory, and disk images should be loaded in non-UI threads.
Note: If cached images are often used, you can consider using them.
ContentProvider, for example, does this in the gallery program.
There is a simple DiskLruCache implementation in the sample code. Then, a more reliable and recommended DiskLruCache (libcore/luni/src/main/java/libcore/io/) is included in Android 4.0.
. You can easily port this implementation to versions before 4.0 (see href="/search?q=disklrucache">Google to see if others have done this!).
Here is an updated version of DiskLruCache:
private DiskLruCache mDiskCache;
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
...
File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
mDiskCache = (this, cacheDir, DISK_CACHE_SIZE);
...
}
class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final String imageKey = (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) {
(key, bitmap);
}
// Also add to disk cache
if (!(key)) {
(key, bitmap);
}
}
public Bitmap getBitmapFromDiskCache(String key) {
return (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.MEDIA_MOUNTED
|| !() ?
().getPath() : ().getPath();
return new File(cachePath + + uniqueName);
}
Detect memory cache in UI threads and disk cache in background threads. Disk operations should never be implemented in UI threads. When the image is processed, the final result will be added to
Memory cache and disk cache are in for future use.
Handle configuration change events
Runtime configuration changes — for example, screen orientation changes — cause Android to destroy the running activity and then use
The new configuration starts the Activity from the new configuration (see Handling Runtime Changes here for details).
You need to be careful to avoid reprocessing all images when configuration changes, thereby improving the user experience.
Fortunately, you already have a good image cache in the use of memory cache. The cache can be passed
Fragment (Fragment will be saved through the setRetainInstance(true) function) to the new Activity
When the Activity is restarted, the Fragment is reattached to the Activity, and you can get the cached object through this Fragment.
Here is an example of saving cache in a Fragment:
private LruCache<string, bitmap=""> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
RetainFragment mRetainFragment = (getFragmentManager());
mMemoryCache = ;
if (mMemoryCache == null) {
mMemoryCache = new LruCache<string, bitmap="">(cacheSize) {
... // Initialize cache here as usual
}
= 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) (TAG);
if (fragment == null) {
fragment = new RetainFragment();
}
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
(savedInstanceState);
<strong>setRetainInstance(true);</strong>
}
}
In addition, you can try to use and not use Fragment to rotate the device's screen orientation to see the specific picture loading situation.