OOM problems caused by Bitmap


Introduction to this section:

In the previous section, we have learned the basic usage of Bitmap, and in this section we are going to discuss the OOM problem of Bitmap. You may or may not have encountered OOM problems caused by Bitmap in actual development. In this section we will Let’s learn about this topic ~ Understand what OOM is, why it causes OOM, and improve the problems caused by Bitmap OOM questions~


#1. What is OOM? Why does it cause OOM?

Answer: Out Of Memory (memory overflow), we all know that the Android system will allocate an independent work space for each APP. Or allocate a separate Dalvik virtual machine so that each APP can run independently without affecting each other! And Android for each Dalvik virtual machines will have a maximum memory limit. If the currently occupied memory plus the memory resources we apply for exceed this limit , the system will throw an OOM error! In addition, don’t confuse this with RAM. Even if there is more than 1G of memory left in RAM, OOM will still happen! Don't mix RAM (physical memory) and OOM! In addition, if there is insufficient RAM, the application will be killed, not just OOM! The maximum memory standard in Dalvik is different for different models. You can call:

ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());

to get the normal maximum memory standard, or type directly on the command line:

adb shell getprop | grep dalvik.vm.heapgrowthlimit

You can also open the system source code/system/build.prop file and look at the information in this part of the file to get:

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=2m
dalvik.vm.heapmaxfree=8m

There are three areas we are concerned about: the initial size of the heapstartsize heap memory, and the application of the heapgrowthlimit standard The largest heap Memory size, heapsize sets the maximum heap memory size for applications using android:largeHeap!

I tried here the normal maximum memory allocation standards of several models on hand:

1.png

You can also try it on your own machine~

Okay, let’s stop talking about it. Regarding the occurrence of OOM problems, we will stop here. Then we will talk about memory management, but it is a big guy. I still can’t chew it now... Let’s take a look at some tips to avoid Bitmap OOM!


2. Summary of tips to avoid OOM caused by Bitmap


1) Use low memory usage encoding method

As mentioned in the previous sectionBitmapFactory.OptionsIn this class, we can set the inPreferredConfig attribute, The default is Bitmap.Config.ARGB_8888, we can change it to Bitmap.Config.ARGB_4444
Bitmap.Config ARGB_4444: Each pixel occupies four bits, that is, A=4, R =4, G=4, B=4, then one pixel occupies 4+4+4+4=16 bits
Bitmap.Config ARGB_8888: Each pixel occupies eight bits, that is, A=8, R=8, G=8, B=8, then one pixel occupies 8+8+8+8=32 bits
The default is ARGB_8888, that is, one pixel occupies 4 bytes!


2) Image compression

is also BitmapFactory.Options. We set the scaling factor through inSampleSize, for example, write 2, that is, the length and width become 1/2 of the original, The picture is 1/4 of the original size. If you don’t scale it, just set it to 1! But it cannot be compressed blindly, after all, this value is too small. Otherwise, the picture will be very blurry, and we need to avoid stretching and deforming the picture, so we need to dynamically calculate this in the program. The appropriate value of inSampleSize, and there is such a method in Options: inJustDecodeBounds, set this parameter to After true, decodeFiel will not allocate memory space, but it can calculate the length and width of the original image, calling options.outWidth/outHeightGet the width and height of the image, and then use a certain algorithm to get the appropriate inSampleSize, thank you 街神 for the code provided - taken from Hongyang blog!

public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    int width = options.outWidth;
    int height = options.outHeight;
    int inSampleSize = 1;
    if (width > reqWidth || height > reqHeight) {
        int widthRadio = Math.round(width * 1.0f / reqWidth);
        int heightRadio = Math.round(height * 1.0f / reqHeight);
        inSampleSize = Math.max(widthRadio, heightRadio);
    }
    return inSampleSize;
}

Then use the above method:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 设置了此属性一定要记得将值设置为false
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeFile(url, options);
options.inSampleSize = computeSampleSize(options,128,128);
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
/* 下面两个字段需要组合使用 */  
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
try {
    bitmap = BitmapFactory.decodeFile(url, options);
} catch (OutOfMemoryError e) {
        Log.e(TAG, "OutOfMemoryError");
}

3. Timely recycling of images

If a large number of Bitmap objects are referenced and the application does not need Display all images simultaneously. Bitmap objects that are not used temporarily can be Recycle it promptly. For some scenarios where you clearly know the usage of images, you can actively recycle them, such as images on the boot page, use When finished, recycle, frame animation, load one, draw one, and release one! Load when used, set null or recycle directly when not displayed! For example: imageView.setImageResource(0); However, in some cases, specific pictures may be repeatedly loaded, released, and reloaded, etc., which is inefficient...


4. Other methods

I have not included the following methods. If you haven’t used it before, you can check the relevant information yourself:

1. Simply manage image resources through SoftReference reference

Create a SoftReference hashmap When using an image, first check whether the hashmap has a softreference and whether the image in the softreference is empty. If it is empty, load the image into the softreference and add it to the hashmap. There is no need to explicitly handle the recycling and release of images in the code, gc will automatically handle the release of resources. This method is simple and practical to handle, and can avoid the inefficiency of repeated loading and releasing of the previous method to a certain extent. But it's not optimized enough.

Sample code:

private Map<String, SoftReference> imageMap 
                                           = new HashMap<String, SoftReference>();

public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) {
    SoftReference reference = imageMap.get(imageUrl);
    if(reference != null) {
        if(reference.get() != null) {
            return reference.get();
        }
    }
    final Handler handler = new Handler() {
        public void handleMessage(final android.os.Message msg) {
            //加入到缓存中
            Bitmap bitmap = (Bitmap)msg.obj;
            imageMap.put(imageUrl, new SoftReference(bitmap));
            if(imageCallBack != null) {
                imageCallBack.getBitmap(bitmap);
            }
        }
    };
    new Thread(){
        public void run() {
            Message message = handler.obtainMessage();
            message.obj = downloadBitmap(imageUrl);
            handler.sendMessage(message);
        }
    }.start();
    return null ;
}

// 从网上下载图片
private Bitmap downloadBitmap (String imageUrl) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream());
        return bitmap ;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    } 
}
public interface ImageCallBack{
    void getBitmap(Bitmap bitmap);
}

2. LruCache + sd caching method

Starting from Android 3.1 version, the official also provides LruCache Perform cache processing when the size of the stored Image is larger than LruCache If the value is set, the pictures with the least recent usage will be recycled, and the system will automatically release the memory!

Usage example:

Steps:

1) First set the memory size of the cached image. I set it here to the mobile phone memory. 1/8, How to obtain mobile phone memory: int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);

2) The key-value pairs in LruCache are the URL and the corresponding image

3) Rewrite a method called sizeOf, which returns the number of pictures.

private LruCache mMemoryCache;
private LruCacheUtils() {
    if (mMemoryCache == null)
        mMemoryCache = new LruCache(
                MAXMEMONRY / 8) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key,
                    Bitmap oldValue, Bitmap newValue) {
                Log.v("tag", "hard cache is full , push to soft cache");
               
            }
        };
}

4) The following methods are to clear the cache, add pictures to the cache, get pictures from the cache, and remove them from the cache.

Removing and clearing the cache is a must, because improper handling of the image cache will cause memory overflow, so you must pay attention.

public void clearCache() {
    if (mMemoryCache != null) {
        if (mMemoryCache.size() > 0) {
            Log.d("CacheUtils",
                    "mMemoryCache.size() " + mMemoryCache.size());
            mMemoryCache.evictAll();
            Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
        }
        mMemoryCache = null;
    }
}

public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (mMemoryCache.get(key) == null) {
        if (key != null && bitmap != null)
            mMemoryCache.put(key, bitmap);
    } else
        Log.w(TAG, "the res is aready exits");
}

public synchronized Bitmap getBitmapFromMemCache(String key) {
    Bitmap bm = mMemoryCache.get(key);
    if (key != null) {
        return bm;
    }
    return null;
}

/**
 * 移除缓存
 * 
 * @param key
 */
public synchronized void removeImageCache(String key) {
    if (key != null) {
        if (mMemoryCache != null) {
            Bitmap bm = mMemoryCache.remove(key);
            if (bm != null)
                bm.recycle();
        }
    }
}

The above content is excerpted from——LruCache, the memory caching technology of image caching, soft reference


Summary of this section:

This section explains it to everyone The causes of OOM problems are also summarized on the Internet to avoid OOM caused by Bitmap. Some of the solutions, because the apps made by the company are all map-based and rarely involve pictures, so the author has not encountered OOM problems. So I’m not very familiar with this~ In the following advanced course of memory management, we will slowly struggle with this OOM problem. Okay, That’s it for this section, thank you~


References: Analysis and Solutions of OOM Problems in Android Applications