Large use of Android Bitmap does not produce OOM "optimization of loading large image resources"

I have been working on a picture browser these days, but OOM occurred when loading a large number of pictures. In order to solve this problem that is usually encountered when loading pictures, I checked the official doc of Google and recorded the knowledge I learned. help.

Let's first take a look at why we should pay attention to bitmap usage optimization: 
1. Mobile devices usually have limited system resources. For example, Android devices can limit each application to a maximum of 16M. In other words, your application must be optimized to occupy less than 16M of memory. 
2. Bitmap happens to be the bulk of the memory. For example, Galaxy Nexus can take a picture with pixels up to 2592x1936. Then bitmap uses ARGB_8888 to parse colors by default. Then this picture occupies about 19M (2592*1936*4 bytes). In this way, the memory limit in the example just now was exceeded immediately. 
3. Android ui often uses bitmap extensively. For example, listview, gridview, viewpager. Generally, many bitmaps are displayed on the page. It also includes the part that is not shown but will be slid to.

In many cases, the picture displayed in our interface is often lower than the actual picture pixel requirements. And using these high-pixel pictures on the interface with low-pixel requirements will not have the advantage of display effect. On the contrary, it will consume a lot of memory and affect efficiency, because this process requires scaling.

BitmapFactory provides a series of analysis methods for different data sources (decodeByteArray(), decodeFile(), decodeResource(), etc.). These methods will allocate memory when constructing the bitmap, so it is easy to cause OOM. Each analysis method provides an optional parameter BitmapFactory.Options to define the properties of the analysis. You can prevent the allocation of memory during parsing by setting the inJustDecodeBounds property to true. At this time, the parsing returns null, but the outWidth, outHeigth and outMimeType are assigned to the options. This technique allows you to obtain the size and type of the bitmap without allocating memory. Look at the code:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Compress a large image

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

public static int calculateInSampleSize(BitmapFactory.Options options,  
        int reqWidth, int reqHeight) {  
    // 源图片的高度和宽度  
    final int height = options.outHeight;  
    final int width = options.outWidth;  
    int inSampleSize = 1;  
    if (height > reqHeight || width > reqWidth) {  
        // 计算出实际宽高和目标宽高的比率  
        final int heightRatio = Math.round((float) height / (float) reqHeight);  
        final int widthRatio = Math.round((float) width / (float) reqWidth);  
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高  
        // 一定都会大于等于目标的宽和高。  
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
    }  
    return inSampleSize;  
}
To use this method, first you need to set the inJustDecodeBounds property of BitmapFactory.Options to true to parse the picture once. Then pass BitmapFactory.Options together with the desired width and height to the calculateInSampleSize method, and you can get the appropriate inSampleSize value. After parsing the picture again, using the newly acquired inSampleSize value, and setting inJustDecodeBounds to false, you can get the compressed picture.

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
        int reqWidth, int reqHeight) {  
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小  
    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeResource(res, resId, options);  
    // 调用上面定义的方法计算inSampleSize值  
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
    // 使用获取到的inSampleSize值再次解析图片  
    options.inJustDecodeBounds = false;  
    return BitmapFactory.decodeResource(res, resId, options);  
} 
The following code is very simple to compress any picture into a 100*100 thumbnail and display it on ImageView.

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

Second, use image caching technology

The following is an example of using LruCache to cache pictures:

private LruCache<String, Bitmap> mMemoryCache;  
  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。  
    // LruCache通过构造函数传入缓存值,以KB为单位。  
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
    // 使用最大可用内存值的1/8作为缓存的大小。  
    int cacheSize = maxMemory / 8;  
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
        @Override  
        protected int sizeOf(String key, Bitmap bitmap) {  
            // 重写此方法来衡量每张图片的大小,默认返回图片数量。  
            return bitmap.getByteCount() / 1024;  
        }  
    };  
}  
  
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
    if (getBitmapFromMemCache(key) == null) {  
        mMemoryCache.put(key, bitmap);  
    }  
}  
  
public Bitmap getBitmapFromMemCache(String key) {  
    return mMemoryCache.get(key);  
} 
In this example, one-eighth of the memory allocated by the system to the application is used as the cache size. In mid-to-high configuration phones, this will probably have 4 megabytes (32/8) of cache space. A full-screen GridView uses 4 800x480 resolution pictures to fill, it will take up about 1.5 megabytes of space (800*480*4). Therefore, this cache size can store 2.5 pages of pictures.
When loading a picture into ImageView, it will first check it in the cache of LruCache. If the corresponding key value is found, the ImageView will be updated immediately, otherwise a background thread will be started to load the image

  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.     }  
BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。
[java]  view plain   copy
  1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
  2.     // 在后台加载图片。  
  3.     @Override  
  4.     protected Bitmap doInBackground(Integer... params) {  
  5.         final Bitmap bitmap = decodeSampledBitmapFromResource(  
  6.                 getResources(), params[0], 100100);  
  7.         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
  8.         return bitmap;  
  9.     }  
  10. }  






Guess you like

Origin blog.csdn.net/xifei66/article/details/76883449