Android briefly introduces image compression and image memory caching

Please indicate the source for reprint: http://blog.csdn.net/guolin_blog/article/details/9316683

 

The main content of this article comes from Android Doc. After I translated it, I did some processing. Friends with good English can also read the original text directly.

 

http://developer.android.com/training/displaying-bitmaps/index.html

 

Efficiently load large images

 

When we write Android programs, we often use many pictures. Different pictures 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 picture library are taken with the camera of the mobile phone, and the resolution of these pictures will be much higher than that of the screen of our mobile phone. Everyone should know that the applications we write all have certain memory restrictions, and the program occupies too much memory, and OOM (OutOfMemory) exceptions are prone to occur. We can see what is the maximum available memory for each application through the code below.

[java] view plain copy
  1. int maxMemory = ( int) (Runtime.getRuntime (). maxMemory () /  1024);  
  2. Log.d("TAG", "Max memory is " + maxMemory + "KB");  

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

 

The BitmapFactory class provides multiple parsing methods (decodeByteArray, decodeFile, decodeResource, etc.) for creating Bitmap objects. We should choose the appropriate method according to the source of the image. For example, the pictures in the SD card can use the decodeFile method, the pictures on the network can use the decodeStream method, and the pictures in the resource file can use the decodeResource method. These methods will try to allocate memory for the already constructed bitmap, which can easily lead to OOM. For this reason, each parsing method provides an optional BitmapFactory.Options parameter. Setting the inJustDecodeBounds property of this parameter to true allows the parsing method to prohibit the allocation of memory for the bitmap, and the return value is no longer a Bitmap object, but is null. Although Bitmap is null, the outWidth, outHeight and outMimeType properties of BitmapFactory.Options will be assigned. This trick allows us to obtain the length, width and MIME type of the image before loading the image, so as to compress the image according to the situation. As shown in the following code:

[java] view plain copy
  1. BitmapFactory.Options options = new BitmapFactory.Options();  
  2. options.inJustDecodeBounds = true;  
  3. BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
  4. int imageHeight = options.outHeight;  
  5. int imageWidth = options.outWidth;  
  6. String imageType = options.outMimeType;  

In order to avoid OOM exceptions, it is best to check the size of each image before parsing, unless you trust the source of the image to ensure that these images will not exceed the available memory of your program.

 

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

  • Estimate the memory required to load the entire image.
  • How much memory are you willing to provide in order to load this image.
  • The actual size of the control used to display this image.
  • The screen size and resolution of the current device.

 

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

 

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

[java] view plain copy
  1. public static int calculateInSampleSize(BitmapFactory.Options options,  
  2.         int reqWidth, int reqHeight) {  
  3.     // height and width of the source image  
  4.     final int height = options.outHeight;  
  5.     final int width = options.outWidth;  
  6.     int inSampleSize = 1;  
  7.     if (height > reqHeight || width > reqWidth) {  
  8.         // Calculate the ratio of the actual width and height to the target width and height  
  9.         final int heightRatio = Math.round((float) height / (float) reqHeight);  
  10.         final int widthRatio = Math.round((float) width / (float) reqWidth);  
  11.         // Select the smallest ratio of width and high school as the value of inSampleSize, which can ensure the width and height of the final image  
  12.         // must be greater than or equal to the width and height of the target.  
  13.         inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
  14.     }  
  15.     return inSampleSize;  
  16. }  

To use this method, first you need to set the inJustDecodeBounds property of BitmapFactory.Options to true and parse the image once. Then pass BitmapFactory.Options along with the desired width and height to the calculateInSampleSize method to get the appropriate inSampleSize value. After parsing the image again, using the newly obtained inSampleSize value, and setting inJustDecodeBounds to false, the compressed image can be obtained.

[java] view plain copy
  1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
  2.         int reqWidth, int reqHeight) {  
  3.     // The first parsing sets inJustDecodeBounds to true to get the image size  
  4.     final BitmapFactory.Options options = new BitmapFactory.Options();  
  5.     options.inJustDecodeBounds = true;  
  6.     BitmapFactory.decodeResource(res, resId, options);  
  7.     // Call the method defined above to calculate the inSampleSize value  
  8.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  9.     // Use the obtained inSampleSize value to parse the image again  
  10.     options.inJustDecodeBounds = false;  
  11.     return BitmapFactory.decodeResource(res, resId, options);  
  12. }  

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

[java] view plain copy
  1. mImageView.setImageBitmap (  
  2.     decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));  

 

Use image caching technology

 

Loading an image in the UI of your application is a simple matter, but when you need to load a lot of images on the interface, things get complicated. In many cases, (such as using components such as ListView, GridView or ViewPager), the picture displayed on the screen can be continuously increased by events such as sliding the screen, which eventually leads to OOM.

 

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

 

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

 

Memory caching technology provides fast access to images that consume a lot of your application's precious memory. The 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 the most recently used objects in the LinkedHashMap with strong references, and remove the least recently used objects from memory before the cached value reaches a preset value.

 

In the past, we have often used an implementation of a very popular memory caching technique called SoftReference or WeakReference. But this method is no longer recommended, because starting from Android 2.3 (API Level 9), the garbage collector will be more inclined to recycle objects that hold soft or weak references, which makes soft and weak references become no longer reliable. In addition, in Android 3.0 (API Level 11), the image data is stored in the local memory, so it cannot be released in a predictable way, which has the potential risk of causing the application to overflow memory and crash.

 

In order to be able to choose an appropriate cache size for LruCache, the following factors should be taken into consideration, for example:

  • How much memory can your device allocate for each application?
  • What is the maximum number of pictures that can be displayed on the device screen at one time? How many images need to be preloaded because there is a chance that they will also be displayed on the screen soon?
  • What is the screen size and resolution of your device? An ultra-high-resolution device (such as the Galaxy Nexus) will require more cache space than a lower-resolution device (such as the Nexus S) to hold the same number of images.
  • The size and size of the images, and how much memory each image will occupy.
  • How often are images accessed? Will some images be accessed more frequently than others? If so, you should probably keep some of the images in memory, or use multiple LruCache objects to differentiate between groups of images.
  • Can you maintain a good balance between quantity and quality? Sometimes, it is more efficient to store multiple low-resolution images and load high-resolution images in a thread in the background.

 

There is no specific cache size that will suffice for all applications, it is up to you. You should analyze your program's memory usage and work out an appropriate solution. A cache that is too small can cause images to be freed and reloaded frequently, which is not beneficial. And a too large cache space may still cause java.lang.OutOfMemory exception.

 

Here is an example of using LruCache to cache images:

[java] view plain copy
  1. private LruCache<String, Bitmap> mMemoryCache;  
  2.   
  3. @Override  
  4. protected void onCreate(Bundle savedInstanceState) {  
  5.     // Get the maximum value of available memory. Using memory beyond this value will cause an OutOfMemory exception.  
  6.     // LruCache passes in the cache value through the constructor, in KB.  
  7.     int maxMemory = ( int) (Runtime.getRuntime (). maxMemory () /  1024);  
  8.     // Use 1/8 of the maximum available memory value as the cache size.  
  9.     int cacheSize = maxMemory / 8;  
  10.     mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
  11.         @Override  
  12.         protected int sizeOf(String key, Bitmap bitmap) {  
  13.             // Override this method to measure the size of each image, and return the number of images by default.  
  14.             return bitmap.getByteCount() / 1024;  
  15.         }  
  16.     };  
  17. }  
  18.   
  19. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  20.     if (getBitmapFromMemCache(key) == null) {  
  21.         mMemoryCache.put(key, bitmap);  
  22.     }  
  23. }  
  24.   
  25. public Bitmap getBitmapFromMemCache(String key) {  
  26.     return mMemoryCache.get(key);  
  27. }  

In this example, one-eighth of the memory allocated by the system to the application is used as the cache size. In a mid-to-high configuration phone, that's about 4MB (32/8) of cache space. A full-screen GridView filled with 4 800x480 resolution images would take up about 1.5 megabytes of space (800*480*4). Therefore, this cache size can store 2.5 pages of images.
When loading an image into ImageView, it will first check in LruCache's cache. If the corresponding key is found, the ImageView is updated immediately, otherwise a background thread is started to load the image.

[java] view plain copy
  1. public void loadBitmap(int resId, ImageView imageView) {  
  2.     final String imageKey = String.valueOf(resId);  
  3.     final Bitmap bitmap = getBitmapFromMemCache(imageKey);  
  4.     if (bitmap != null) {  
  5.         imageView.setImageBitmap(bitmap);  
  6.     } else {  
  7.         imageView.setImageResource(R.drawable.image_placeholder);  
  8.         BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
  9.         task.execute(resId);  
  10.     }  
  11. }  

The BitmapWorkerTask also puts the key-value pair of the newly loaded image into the cache.

[java] view plain copy
  1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
  2.     // Load the image in the background.  
  3.     @Override  
  4.     protected Bitmap doInBackground(Integer... params) {  
  5.         final Bitmap bitmap = decodeSampledBitmapFromResource(  
  6.                 getResources(), params[0], 100, 100);  
  7.         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
  8.         return bitmap;  
  9.     }  
  10. }  

If you have mastered the above two methods, whether you want to load large pictures in the program or load a large number of pictures, you don't have to worry about the problem of OOM! However, it is only a theoretical introduction. I don't know if you can fully understand it. In the following article I will demonstrate how to use the above techniques flexibly in actual programs to avoid program OOM. If you are interested, please continue to read the  Android photo wall application implementation, no matter how many pictures you are not afraid of crashing

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324970359&siteId=291194637