SoFunction
Updated on 2025-03-09

Android loading large images, multi images and LruCache cache detailed introduction

When we write Android programs, we often use many pictures. Different pictures will always have different shapes and sizes, but in most cases, these pictures will be larger than the size required by our program. For example, most of the pictures displayed in the system image library are taken with a mobile phone camera, and the resolution of these pictures will be much higher than the resolution of our mobile phone screen. As you should know, the applications we write have certain memory limits, and if the program occupies too high memory, it is prone to OOM (OutOfMemory) exceptions. We can see the maximum available memory for each application through the following code

int maxMemory = (int) (().maxMemory() / 1024); 
("TAG", "Max memory is " + maxMemory + "KB"); 

Therefore, when displaying high-resolution pictures, it is best to compress the pictures first. The compressed image size should be similar to the control used to display it. Displaying a super large image on a small ImageView will not bring any visual benefits, but it will take up a lot of our valuable memory and may have negative performance impacts. Let’s take a look at how to properly compress a large image so that it can be displayed at the best size while preventing the appearance of OOM.

The BitmapFactory class provides multiple parsing methods (decodeByteArray, decodeFile, decodeResource, etc.) for creating Bitmap objects. We should choose the appropriate method based on the source of the image. For example, the images in the SD card can use the decodeFile method, the images on the network can use the decodeStream method, and the images in the resource file can use the decodeResource method. These methods will try to allocate memory for the already built bitmap, which will easily lead to OOM. For this purpose, each parsing method provides an optional

Parameter, setting the inJustDecodeBounds property of this parameter to true can make the parsing method prohibit allocating memory for bitmap, and the return value is no longer a Bitmap object, but null. Although Bitmap is null, the outWidth, outHeight and outMimeType properties are all assigned. This technique allows us to obtain the length and width value and MIME type of the image before loading it, so as to compress the image according to the situation. The following code looks like:

 options = new (); 
 = true; 
(getResources(), , options); 
int imageHeight = ; 
int imageWidth = ; 
String imageType = ; 

In order to avoid OOM exceptions, it is best to check the size of the picture when parsing each picture, unless you trust the source of the picture very much, and ensure that these pictures will not exceed the available memory of your program.

Now that the size of the picture is known, we can decide whether to load the entire picture into memory or load a compressed version of the picture into memory. The following factors are what we need to consider:

  1. Estimate the memory required to load the entire picture
  2. How much memory do you want to provide to load this image
  3. The actual size of the control used to show this image
  4. Current device screen size and resolution

For example, your ImageView is only 128*96 pixels in size, just to display a thumbnail, it is obviously not worth loading a 1024*768 pixel image into memory at this time.

So how can we compress the image? This can be achieved by setting the value of inSampleSize. For example, if we have a picture of 2048*1536 pixels, set the value of inSampleSize to 4, we can compress the picture to 512*384 pixels. Originally, loading this picture requires 13M of memory, but after compression, it only requires 0.75M (assuming that the picture is of ARGB_8888 type, that is, each pixel takes 4 bytes). The following method can calculate the appropriate inSampleSize value based on the width and height of the incoming:

public static int calculateInSampleSize( options, 
  int reqWidth, int reqHeight) { 
 // Height and width of the source image final int height = ; 
 final int width = ; 
 int inSampleSize = 1; 
 if (height > reqHeight || width > reqWidth) { 
  // Calculate the ratio of actual width and height to target aspect and height  final int heightRatio = ((float) height / (float) reqHeight); 
  final int widthRatio = ((float) width / (float) reqWidth); 
  // Choose the minimum ratio of width and high school as the value of inSampleSize, so that the width and height of the final picture can be guaranteed.  // It will definitely be greater than or equal to the width and height of the target.  inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 
 } 
 return inSampleSize; 
} 

Using this method, first you need to set the inJustDecodeBounds property to true and parse the image once. Then pass the desired width and height into the calculatedInSampleSize method, and you can get the appropriate inSampleSize value. Then parse the image again, use the newly obtained inSampleSize value, and set inJustDecodeBounds to false, and you can get the compressed image

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, 
  int reqWidth, int reqHeight) { 
 // Set inJustDecodeBounds to true for the first parsing to get the image size final  options = new (); 
  = true; 
 (res, resId, options); 
 // Call the method defined above to calculate the inSampleSize value  = calculateInSampleSize(options, reqWidth, reqHeight); 
 // Use the obtained inSampleSize value to parse the image again  = false; 
 return (res, resId, options); 
} 

The following code simply compresses any image into a 100*100 thumbnail and displays it on the ImageView.


    decodeSampledBitmapFromResource(getResources(), , 100, 100)); 

LruCache Cache

Loading an image in your application's UI interface is a very simple thing, but when you need to load a lot of images on the interface, the situation becomes complicated. In many cases, (such as using components such as ListView, GridView or ViewPager), the images displayed on the screen can continue to increase by sliding the screen and other events, ultimately leading to OOM

In order to ensure that memory usage is always maintained within a reasonable range, images that have been removed from the screen are usually recycled. At this time, the garbage collector will also think that you no longer hold references to these images, so it performs GC operations on these images. It is very good to use this idea to solve the problem, but in order to enable the program to run quickly and quickly load the picture on the interface, you must also consider that after some pictures are recycled, the user slides it back into the screen. At this time, reloading the picture you just loaded is undoubtedly a performance bottleneck. You need to find a way to avoid this situation.

At this time, using memory caching technology can solve this problem well, which allows components to quickly reload and process images. Let's take a look at how to use memory caching technology to cache images, so that your application can improve response speed and fluency when loading many images.

Memory caching technology provides a quick way to access images that take up a lot of valuable memory of the application. The most core class is LruCache (this class is provided in the Android-support-v4 package). This class is very suitable for caching images. Its main algorithm principle is to store recently used objects in LinkedHashMap with strong references, and remove the most recently used objects from memory before the cache value reaches the preset value.

In the past, we often used an implementation of a very popular memory caching technology, namely, SoftReference or WeakReference. But this method is no longer recommended, because starting with Android 2.3 (API Level 9), garbage collectors will tend to recycle objects holding soft or weak references, which makes soft and weak references no longer reliable. In addition, in Android 3.0 (API Level 11), the data of the image will be stored in local memory, so it cannot be released in a foreseeable way, which has the potential risk of causing the application's memory to overflow and crash.

In order to be able to select a suitable cache size for LruCache, there are several factors that should be considered, for example:

  1. How much memory can your device allocate to each application?
  2. How many pictures can be displayed at a time on the device screen? How many images need to be preloaded, as it is possible that they will be displayed on the screen soon?
  3. What are the screen size and resolution of your device? An ultra-high resolution device (such as the Galaxy Nexus) requires a larger cache space when holding the same number of images than a lower resolution device (such as the Nexus S).
  4. The size and size of the picture, and how much memory space will each picture occupy
  5. How often do images be accessed? Will some pictures be accessed more frequently than others? If so, you might want to have some images reside in memory, or use multiple LruCache objects to distinguish different groups of images
  6. Can you maintain a good balance between quantity and quality? Sometimes, it is more effective to store multiple low-pixel pictures in the background to open threads to load high-pixel pictures.

There is no specified cache size that can satisfy all applications, it is up to you. You should analyze the application memory usage and then develop a suitable solution. A cache space that is too small may cause images to be released and reloaded frequently, which is not beneficial. A cache space that is too large may still cause an exception

example

Here is an example of using LruCache to cache images:

private LruCache<String, Bitmap> mMemoryCache; 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
 // Get the maximum value of available memory. Exceeding this value using memory will cause an OutOfMemory exception. // LruCache passes in cache values ​​through the constructor, in KB. int maxMemory = (int) (().maxMemory() / 1024); 
 // Use 1/8 of the maximum available memory value as the cache size. int cacheSize = maxMemory / 8; 
 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
  @Override 
  protected int sizeOf(String key, Bitmap bitmap) { 
   // Rewrite this method to measure the size of each image, and the number of images is returned by default.   return () / 1024; 
  } 
 }; 
} 

public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
 if (getBitmapFromMemCache(key) == null) { 
  (key, bitmap); 
 } 
} 

public Bitmap getBitmapFromMemCache(String key) { 
 return (key); 
} 

In this example, one eighth of the memory allocated to the application by the system is used as the cache size. In mobile phones with medium and high configurations, this will probably have about 4 megabytes (32/8) of cache space. A full-screen GridView uses 4 800x480 resolution pictures to fill, which will probably occupy 1.5 megabytes of space (800*480*4). Therefore, this cache size can store 2.5 pages of pictures.

When loading an image into an ImageView, it will first be checked in the LruCache cache. If the corresponding key value is found, the ImageView will be updated immediately. Otherwise, a background thread will be opened to load the image

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(imageView); 
  (resId); 
 } 
} 

BitmapWorkerTask also needs to put the key-value pairs of newly loaded images into the cache

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
 // Load the picture in the background. @Override 
 protected Bitmap doInBackground(Integer... params) { 
  final Bitmap bitmap = decodeSampledBitmapFromResource( 
    getResources(), params[0], 100, 100); 
  addBitmapToMemoryCache((params[0]), bitmap); 
  return bitmap; 
 } 
} 

After mastering the above two methods, no matter whether you want to load super large images in the program or load a large number of images, you don’t have to worry about OOM! But I don’t know if you can fully understand it just a theoretical introduction.

Thank you for reading, I hope it can help you. Thank you for your support for this site!