放牧代码和思想
专注自然语言处理、机器学习算法
    愛しさ 優しさ すべて投げ出してもいい

解决BitmapFactory.decode OutOfMemory问题

今天项目app出bug了,在android2.3上重复打开/关闭一个activity 6次后,第7次一定会崩溃,抛出 OOM 异常,异常由BitmapFactory.decode系列方法抛出。

其实这并非是我滥用内存造成的,而是Bitmap对象有个很怪异的表现。在android 3.0以下,Bitmap与普通Java对象不同,普通Java对象创建于 VM heap 中,而Bitmap的大部分数据创建于 native memory 上,只在VM heap保留少数数据。当Bitmap对象的引用计数减少为0的时候,清理的是VM heap里的小部分数据,大部分数据没有被回收,相当于内存泄露。当然,只有等到整个progress退出的时候,这部分内存会还回去。

于是就造成了上述bug,像极了Win32里的GDI Object泄露。

好在Bitmap提供了一个recycle()方法,配合System.gc()可以在退出的时候返还所有内存。

我写了个demo来再现这个问题:

package com.hankcs.BitmapRecyle;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

public class MyActivity extends Activity
{
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        // 初始占用7.3MB
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                for (int i = 1; i <= 100; ++i)
                {
                    String msg = "执行:" + i + "次。";
                    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.maho);
                    Log.d(getClass().toString(), msg + bitmap);
                    // 尝试注释以下两句来观察内存的消耗
                    bitmap.recycle();
                    System.gc();
                    // 如果不调用bitmap.recycle()也不调用System.gc();则内存占用17MB
                    // 如果只调用bitmap.recycle()不调用System.gc();则内存占用17MB
                    // 如果不调用bitmap.recycle()只调用System.gc();则内存占用17MB
                    // 如果同时调用bitmap.recycle()和System.gc();则内存占用7.4MB
                }
            }
        });
    }
}

一个activity加载后占用7.4MB内存:

按下按钮后调用100次decode,但是不调用recycle,内存飙升至17MB:

同时调用recycle和gc,内存几乎不变。

看来问题的解决就是同时调用bitmap.recycle()和System.gc();

stackoverflow上也有人谈到了相同的问题:

Bitamp.recycle isn't required to be called, as the garbage collector will clean up bitmaps on its own eventually (as long as there are no references). Bitmaps in Android are created in native memory, not on the VM heap, so the actual Bitmap object on the VM heap is very small as it doesn't contain any actual bitmap data. (EDIT: no longer the case as of Android 3.0+) The real size of the bitmap will still be counted against your heap usage for purposes of GC and making sure your app doesn't use too much memory.

However, the GC seems to be a little moody when it comes to Bitmaps. If you just remove all hard references, it would sometimes (in my case) hang onto the Bitmaps for a little while longer, perhaps because of the weird way Bitmap objects are allocated/counted. Bitmap.recycle seems to be good for getting the GC to collect that object more quickly.

Either way, you won't leak memory if you don't call Bitmap.recycle as long as you don't keep hard references accidentally. You may encounter OutOfMemoryErrors if you try to allocate too many bitmaps at once or too large bitmaps without calling .recycle, though.

EDIT: It is important to note that as of Android 3.0, Bitmaps are no longer allocated in native memory. The are allocated on the VM heap like any other Java object. However, what I said about not needing to call recycle still applies.

http://stackoverflow.com/questions/4959485/bitmap-bitmap-recycle-weakreferences-and-garbage-collection

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 解决BitmapFactory.decode OutOfMemory问题

评论 欢迎留言

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

我的作品

HanLP自然语言处理包《自然语言处理入门》