AsyncTask和内存泄漏

时间:2013-01-08 22:24:45

标签: java android android-asynctask

我正在编写一个简单的地图应用程序,从服务器下载osm磁贴,为每个磁贴生成一个新的AsyncTask。一些AsyncTasks运行完成,一些在它们有机会运行之前被取消(如果我平移屏幕)。当它们似乎泄漏时可能是。我首先通过检查我的瓷砖位图的寿命并看到它们泄漏来发现这一点。在对AsyncTask中的位图(间接)引用进行归零后,位图停止泄漏,但它让我思考为什么所述引用很重要并且发现AsycTasks的数量不断增长,尽管所有努力都要关闭对它们的引用。我运行MAT分析,这是我需要帮助的时候。任何人都可以帮我解释image(对不起,我没有足够的stackoverflow布朗点来直接附加图像)。

显示的树条目显示了许多泄露的AsyncTasks(类型为FileCacheTask)中的一个,其子项表示对它的所有传入引用。我知道'指示'是垃圾收集器。另外两个,表示为$ 0(暗示与外层的亲和关系)类型android.os.AsyncTask(这与FileCacheTask的外部类没有任何关系,不可否认,FileCacheTask是tile工厂内部的非静态内部类) ,通过mWorker成员持有对我泄露的对象的引用,是我无法解释的,显然无法摆脱。我尝试按照路径并查找对android.os.AsyncTask对象的传入引用,但看到一些调度内部和我的代码没有任何关系)。 有什么想法可能是android.os.AsyncTask对象,这个对象被称为$ 0?

修改即可。 根据建议,我(有点)将问题提炼为可消化的代码片段。我说的有点,因为它看似表现出相同的行为,但我不能确定它真的遭受同样的问题。它仍然表现得密码。这是代码:

public class TaskLeak {
static public int DDeletedTasks; //these are all for debug/tracing purposes
static public int DCreatedTasks;
static public int DCancelled;

private class PrivateTask extends AsyncTask<Void, Void, Void>
{
    private int mTaskId;
    private boolean mFinished;
    private Bitmap mBitmap;
    public PrivateTask()
    {
        mTaskId = ++DCreatedTasks;
                //allocating a bitmap to give this object a meaningful weight for GC to consider.
        mBitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888);
        //Log.d("Tasks", "(constructor) Task count:"+ mTaskId+" tasks in existence:"+(DCreatedTasks-DDeletedTasks));
        execute();
    }

    @Override
    protected Void doInBackground(Void... params) {
        try {
            int sleepForMs = mRandomGenerator.nextInt(100);
            //emulating blocking download action for a random few milliseconds.
            Thread.sleep(sleepForMs);
            //Log.d("Tasks", "Waking up from sleep, task:" + mTaskId);
        } catch (InterruptedException e) {
            //Log.d("Tasks", "Can't sleep, task:" + mTaskId);
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) 
    {
        mFinished = true;
        maintain();
    }

    @Override
    protected void onCancelled()
    {
        DCancelled++;
        onPostExecute(null);
    }

    @Override
    protected void finalize()
    {
        ++DDeletedTasks;
        Log.d("Tasks", "(destructor) Task count:"+ mTaskId+" tasks in existence:"+(DCreatedTasks-DDeletedTasks));
    }       

    public boolean finished()
    {
        return mFinished;
    }       
}

private final static int KMaxTasks = 100;
private Random mRandomGenerator;
private final int mMaxCycles;
private int mCycles;
private final Vector<PrivateTask> mTasks;
public TaskLeak(int maxCycles)
{
    mMaxCycles = maxCycles;
    mRandomGenerator = new Random();
    mTasks = new Vector<PrivateTask>();
    for (int i = 0; i < KMaxTasks; i++)
    {
        mTasks.add(new PrivateTask());
    }
}

public void maintain()
{
    Log.d("Tasks", "***maintain(), tasks held:"+mTasks.size()+" there are "+ (mMaxCycles - ++mCycles)+" cycles left to go");
    for (int i = 0; i < mTasks.size();)
    {
        if (mTasks.get(i).finished())
        {
            mTasks.remove(i);
            if (mCycles < mMaxCycles)
            {
                mTasks.add(new PrivateTask());
            }
        }
        else
        {
            i++;
        }
    }

    if ((mCycles % 10) == 0 )
    {
        DCancelled++;
        mTasks.get(5).cancel(true);
    }

    if (mTasks.size() == 0)
    {
        new TestMemoryGrabber(200).test();
        Log.d("Tasks", "maintain() has finished!");
        Log.d("Tasks", "maintain(). There have been "+DCreatedTasks+" tasks created.");
        Log.d("Tasks", "maintain(). There have been "+DDeletedTasks+" tasks deleted.");
        Log.d("Tasks", "maintain(). There have been "+(DCreatedTasks-DDeletedTasks)+" tasks remaining.");   
        Log.d("Tasks", "maintain(). There have been "+DCancelled+" tasks cancelled.");
    }
}

当执行1000个循环时,它会在内存异常中途中途(在循环#570上)失败,尽管在任何时候我都维持不超过100个对PrivateTask对象的引用。这本身就有些令人费解,因为GC应该不断为新条目腾出空间。为什么不呢?

这是关于oom异常何时发生的LogCat:

01-12 16:37:50.902: D/Tasks(3235): (destructor) Task count:568 tasks in existence:156
01-12 16:37:50.993: D/Tasks(3235): ***maintain(), tasks held:100 there are 430 cycles left to go
01-12 16:37:51.062: I/dalvikvm-heap(3235): Clamp target GC heap from 49.251MB to 48.000MB
01-12 16:37:51.062: D/dalvikvm(3235): GC_FOR_ALLOC freed 257K, 2% free 48337K/48903K, paused 70ms, total 71ms
01-12 16:37:51.062: D/Tasks(3235): (destructor) Task count:569 tasks in existence:156
01-12 16:37:51.092: D/Tasks(3235): ***maintain(), tasks held:100 there are 429 cycles left to go
01-12 16:37:51.222: I/dalvikvm-heap(3235): Clamp target GC heap from 49.251MB to 48.000MB
01-12 16:37:51.232: D/dalvikvm(3235): GC_FOR_ALLOC freed 257K, 2% free 48337K/48903K, paused 139ms, total 139ms
01-12 16:37:51.242: D/Tasks(3235): ***maintain(), tasks held:100 there are 428 cycles left to go
01-12 16:37:51.312: I/dalvikvm-heap(3235): Clamp target GC heap from 49.502MB to 48.000MB
01-12 16:37:51.322: D/dalvikvm(3235): GC_FOR_ALLOC freed <1K, 1% free 48593K/48903K, paused 70ms, total 72ms
01-12 16:37:51.322: I/dalvikvm-heap(3235): Forcing collection of SoftReferences for 262160-byte allocation
01-12 16:37:51.412: I/dalvikvm-heap(3235): Clamp target GC heap from 49.494MB to 48.000MB
01-12 16:37:51.412: D/dalvikvm(3235): GC_BEFORE_OOM freed 9K, 1% free 48584K/48903K, paused 86ms, total 86ms
01-12 16:37:51.412: E/dalvikvm-heap(3235): Out of memory on a 262160-byte allocation.
01-12 16:37:51.412: I/dalvikvm(3235): "main" prio=5 tid=1 RUNNABLE
01-12 16:37:51.412: I/dalvikvm(3235):   | group="main" sCount=0 dsCount=0 obj=0x40a14568 self=0x2a00b9e0
01-12 16:37:51.412: I/dalvikvm(3235):   | sysTid=3235 nice=0 sched=0/0 cgrp=apps handle=1073870640
01-12 16:37:51.412: I/dalvikvm(3235):   | schedstat=( 21236163363 7517071458 4002 ) utm=1940 stm=183 core=0
01-12 16:37:51.412: I/dalvikvm(3235):   at android.graphics.Bitmap.nativeCreate(Native Method)
01-12 16:37:51.412: I/dalvikvm(3235):   at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
01-12 16:37:51.412: I/dalvikvm(3235):   at android.graphics.Bitmap.createBitmap(Bitmap.java:620)

1 个答案:

答案 0 :(得分:0)

在查看了您的代码,日志和解释后,我猜这个OutOfMemoryError是由于在内存中存储了太多位图而导致的。

当您创建每个PrivateTask时,在它开始运行之前,您正在初始化要保留在内存中的Bitmap。有超过100个任务存在,你正在推动你的内存限制。 Logcat行如下所示:

01-12 16:37:51.412: D/dalvikvm(3235): GC_BEFORE_OOM freed 9K, 1% free 48584K/48903K, paused 86ms, total 86ms

至于缓解问题,我建议的第一个解决方案是确保一旦取消每个任务,就在你的Bitmap对象上明确调用recycle()。我怀疑即使PrivateTasks被取消,也会保留一些对它们的引用,这导致只有非常少量(1%ish)的内存才能在GC发生时被释放,因为系统不是摧毁它们,或者反过来,你的位图。或者,GC可以正确地处理这个问题,但是在您的活动PrivateTasks中有足够的位图将您推送到内存限制,这也会导致没有任何垃圾收集,因为系统无法找到任何要收集的内容!如果您经常需要从服务器下载如此多的位图,您可能需要考虑将它们存储在磁盘上,并且只在需要时将它们检索到内存中,尽管这可能会导致显着的性能损失。

以下是有关位图管理的一些链接和其他SO问题:

Displaying Bitmaps Efficiently from the Android Dev Site(特别参见缓存位图一节)

Solutions for someone who had a similar problem

You could try compressing the Bitmaps once they finish downloading, depending on quality. Remember to recycle the reference to the original after compression!

More info on what's going on under the hood when you allocate memory for a bitmap.

作为旁注,我很好奇你正在测试什么类型的设备/模拟器,尤其是SDK版本。随着更新的硬件,为Android应用程序提供的内存量增加,因此您可能会在不同的手机上看到不同的结果测试。此外,我仍然认为许多设备(越来越多的设备,如Gingerbread失去市场份额和ICS / Jellybean收益)将可能连续执行这些操作,这也将改变您获得的结果,尽管似乎不是这个特定问题的根源,将更加关注您应用的未来兼容性。

希望这有帮助!