2013-01-08 111 views
0

我正在编写一个简单的地图应用程序,从服务器为每个图块产生一个新的AsyncTask,下载osm图块。一些AsyncTasks运行完成,一些AsyncTasks在他们有机会运行之前被取消(以防万一我平移了屏幕)。它可能是,当他们似乎泄漏。我首先通过检查我的瓦片位图的寿命并发现它们泄漏来发现它。在将AsyncTask中的位图的(间接)引用归零后,位图停止泄漏,但它让我思索为什么说引用很重要,并发现AsycTasks的数量不断增长,尽管尽力关闭对引用的引用。我运行MAT分析,这是当我需要帮助。任何人都可以帮我解释image(对不起,我没有足够的棕色点来直接附加图片)。AsyncTask和内存泄漏

显示的树条目呈现了许多泄漏的AsyncTasks(类型FileCacheTask)中的一个,子节点表示对它的所有传入引用。我明白'指涉'是垃圾收集器。另外两个,表示为$ 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的关于当OOM发生异常:

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) 
+4

logcat /部分代码与异步任务 - 请放在这里,请 – deadfish

+2

没有发布代码很难帮助您,但在阅读您的文章后,这里有一个提示:它违背了最佳实践,以您的方式发射AsyncTasks描述。查看这里的“执行顺序”部分。 http://developer.android.com/reference/android/os/AsyncTask.html从Honeycomb(Android 3.0)开始,AsyncTask池中只有一个线程,所以你的磁贴下载将在任何Android版本的post-姜饼。使用自定义的ThreadPool将缓解这个潜在的问题,并且在这个过程中也可能帮助你解决内存泄漏问题。 – MattDavis

+0

感谢您的第一次尝试。我要做的就是尝试将问题提炼成一段代码,用来演示behvaiour和转发。可能需要我一两天。 @Matt:我的实施的哪一方面违背了最佳实践?我正在产生每个任务的瓦片,而不是实现一个瓦片队列并让单个任务处理队列? – Remster

回答

0

审查你的代码,日志和解释之后,我猜,这导致的OutOfMemoryError具有存储在内存中太多的位图。

当您创建每个PrivateTask,甚至在它开始运行之前,您正在初始化一个位图以保存在内存中。有超过100个任务存在,你正在推动你的内存限制。 logcat的线,如下面的说明这一点:

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

对于缓解这个问题,1号解决方案,我可以推荐的是确保一旦取消了每个任务,显式调用你的位图对象recycle()。我怀疑即使PrivateTasks被取消了,但仍然维护了一些对它们的引用,当GC发生时,这只会导致内存中非常少量(1%ish)的内存被释放,因为系统不是摧毁它们,或反过来,你的Bitmaps。另外,GC可以正确地处理这个问题,但是活动的PrivateTasks中有足够的Bitmap将你推到内存限制,这也会导致没有任何东西被垃圾收集,因为系统找不到任何东西来收集!如果您经常需要从您的服务器上下载很多位图,您可能需要考虑将它们存储在磁盘上,并根据需要将它们检索到内存中,但这可能会导致显着的性能下降。

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

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.

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

希望这会有所帮助!

+0

再次感谢Matt。, – Remster

+0

再次感谢Matt,但这不是问题所在。上面的代码只是我真正运行的一个很好的模拟,我真正运行的是位图管理没有问题。位图功能如上(以及评论):“给这个对象一个有意义的权重供GC考虑。”是的,OOM异常是因为在内存中存储了太多的位图而出现的,但为什么会有这么多,如果我的代码只保留引用到100?为什么我会在OOM点观察存在的156个位图保存任务。 GC为什么不释放它们来满足内存饥饿的请求? – Remster