AsyncTask和内存泄漏



我正在编写一个简单的地图应用程序,从服务器下载osm瓷砖,为每个瓷砖生成一个新的AsyncTask。一些asynctask运行到完成,一些在它们有机会运行之前被取消(以防我平移屏幕)。也许是在那时,当它们出现泄漏时。我第一次发现这个问题是通过检查我的tile位图的生命周期,发现它们会泄漏。在取消AsyncTask中对位图的(间接)引用后,位图停止泄漏,但这让我思考为什么所说的引用很重要,并发现尽管所有努力关闭对它们的引用,但AsyncTask的数量仍在不断增长。我做了MAT分析,这是我需要帮助的时候。谁能帮我解释图像(对不起,我没有足够的stackoverflow棕色点来直接附加图像)。

所示的树条目显示了许多泄漏的asynctask(类型为FileCacheTask)中的一个,其子条目表示对它的所有传入引用。我理解"referent"是指垃圾收集器。另外两个,表示为$0(暗示与外部类的亲和力)类型android.os.AsyncTask(这与FileCacheTask的外部类没有任何关系,诚然FileCacheTask是一个非静态内部类在瓷砖工厂),持有引用到我泄漏的对象似乎通过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应该不断为新条目腾出空间。为什么不呢?

这是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)

在审查了你的代码,日志和解释之后,我猜这个OutOfMemoryError是由于内存中存储了太多的位图而引起的。

当你创建每个PrivateTask时,在它开始运行之前,你就初始化了一个保存在内存中的位图。有超过100个任务存在,你正在推动你的内存极限。像下面这样的日志行表示:

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

为了缓解这个问题,我推荐的第一个解决方案是确保在每个任务被取消后,在Bitmap对象上显式调用recycle()。我怀疑,即使privatetask被取消,对它们的一些引用仍在维护中,这导致只有非常少量(1%)的内存在GC发生时被释放,因为系统只是没有破坏它们,或者反过来,你的位图。或者,GC可以完全正确地处理这个问题,但是在您的活动PrivateTasks中有足够的位图将您推到内存限制,这也会导致没有垃圾收集,因为系统找不到任何要收集的东西!如果你经常需要从你的服务器下载这么多的位图,你可能想把它们存储在磁盘上,只在需要的时候检索到内存中,尽管这可能会导致显著的性能下降。

这里有一些关于位图管理的链接和其他SO问题:

从Android Dev站点高效地显示位图(特别参见缓存位图部分)

遇到类似问题的人的解决方案

你可以尝试压缩位图一旦他们完成下载,这取决于质量。记住在压缩后要循环引用原始文件!

当你为位图分配内存时,下面发生了什么。

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

希望这对你有帮助!

相关内容

  • 没有找到相关文章

最新更新