在Android上避免内存泄漏

22

我刚刚阅读了Romain Guy的一篇博客文章,讲述如何避免在Android中出现内存泄漏。

在文章中,他给出了以下示例:

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);

  setContentView(label);
}

Romain说:

这个示例是泄漏Context的最简单情况之一。

我的问题是,你如何正确地修改它?

就像这样?

TextView label = new TextView(Context.getApplicationContext());

我测试了两种方式,结果都相同。我找不到差异。而且我认为this比应用程序上下文更正确。因为this是对Activity的引用,也就是说TextView属于该Activity

有人能给我解释一下吗?


它会占用整个应用程序的生命周期... - vnshetty
4个回答

17

这段代码的实际问题不是传递给create drawable的上下文,而是私有的静态Drawable sBackground。该静态Drawable是使用Activity作为上下文创建的,在这种情况下,静态引用了一个引用Activity的Drawable,这就是泄漏的原因。只要该引用存在,Activity将一直保留在内存中,导致其所有视图泄漏。

因此,应该使用应用程序上下文创建Drawable,而不是TextView。使用“this”创建TextView是完全可以的。

编辑:实际上,这可能没有什么大的区别,问题在于一旦Drawable绑定到一个视图上,就会有一个引用指向该视图,而该视图又引用了Activity。因此,在退出Activity时需要“解除绑定”Drawable。


1
应该使用应用程序上下文创建Drawable-->你认为应该这样做sBackground = getApplicationContext().getResources().getDrawable(R.drawable.icon);,对吗? - Judy
@Judy 是的,那应该更好。 - Gregory
@Judy 实际上,不,那不应该有任何影响,问题在于当你将可绘制对象绑定到视图时,它会得到一个对其的引用,这个引用又会持有一个对活动的引用。因此,你实际上需要从视图中清除可绘制对象的绑定关系。 - Gregory
1
"解除视图中的可绘制对象绑定"-->如何解除视图中的可绘制对象绑定?就像这段代码一样:`@Override protected void onDestroy() { // TODO Auto-generated method stub label.setBackgroundDrawable(null); super.onDestroy();}' 请告诉我如何实现。谢谢。" - Judy
在你的可绘制对象上调用setCallback(null)。 - Gregory

1

我不确定自从你阅读过后,Romain是否已经更新了他的博客文章,但他非常清楚如何避免泄漏,甚至指出了Android操作系统中的一个示例。请注意,我通过archive.org修复了Romain博客文章中的链接错误。

这个例子是泄露上下文的最简单情况之一,你可以在Home屏幕源代码中看到我们如何解决它(查找unbindDrawables()方法),通过在活动销毁时将存储的可绘制对象的回调设置为null。有趣的是,有些情况下你可以创建一系列泄漏的上下文,它们非常糟糕,会让你很快耗尽内存。
避免与上下文相关的内存泄漏有两种简单的方法。最明显的方法是避免将上下文逃逸到其自身范围之外。上面的例子展示了静态引用的情况,但内部类及其对外部类的隐式引用同样危险。第二种解决方案是使用应用程序上下文。这个上下文将随着应用程序的生命周期而存在,并且不依赖于活动的生命周期。如果您计划保留需要上下文的长寿命对象,请记住应用程序对象。您可以通过调用Context.getApplicationContext()或Activity.getApplication()轻松获取它。
总之,为了避免与上下文相关的内存泄漏,请记住以下几点:
- 不要保留上下文活动的长期引用(对活动的引用应该具有与活动本身相同的生命周期) - 尝试使用上下文应用程序而不是上下文活动 - 如果您无法控制内部类的生命周期,请避免在活动中使用非静态内部类,使用静态内部类并在其中对活动进行弱引用。解决此问题的方法是使用带有对外部类的WeakReference的静态内部类,例如ViewRoot及其W内部类。 - 垃圾收集器不能保证防止内存泄漏。

0

我不知道你的应用程序是否遇到了这个问题,但是我已经创建了一个可插拔的解决方案,可以解决所有标准Android类的内存泄漏问题:http://code.google.com/p/android/issues/detail?id=8488#c51

public abstract class BetterActivity extends Activity
{
  @Override
  protected void onResume()
  {
    System.gc();
    super.onResume();
  }

  @Override
  protected void onPause()
  {
    super.onPause();
    System.gc();
  }

  @Override
  public void setContentView(int layoutResID)
  {
    ViewGroup mainView = (ViewGroup)
      LayoutInflater.from(this).inflate(layoutResID, null);

    setContentView(mainView);
  }

  @Override
  public void setContentView(View view)
  {
    super.setContentView(view);

    m_contentView = (ViewGroup)view;
  }

  @Override
  public void setContentView(View view, LayoutParams params)
  {
    super.setContentView(view, params);

    m_contentView = (ViewGroup)view;
  }

  @Override
  protected void onDestroy()
  {
    super.onDestroy();

    // Fixes android memory  issue 8488 :
    // http://code.google.com/p/android/issues/detail?id=8488
    nullViewDrawablesRecursive(m_contentView);

    m_contentView = null;
    System.gc();
  }

  private void nullViewDrawablesRecursive(View view)
  {
    if(view != null)
    {
      try
      {
        ViewGroup viewGroup = (ViewGroup)view;

        int childCount = viewGroup.getChildCount();
        for(int index = 0; index < childCount; index++)
        {
          View child = viewGroup.getChildAt(index);
          nullViewDrawablesRecursive(child);
        }
      }
      catch(Exception e)
      {          
      }

      nullViewDrawable(view);
    }    
  }

  private void nullViewDrawable(View view)
  {
    try
    {
      view.setBackgroundDrawable(null);
    }
    catch(Exception e)
    {          
    }

    try
    {
      ImageView imageView = (ImageView)view;
      imageView.setImageDrawable(null);
      imageView.setBackgroundDrawable(null);
    }
    catch(Exception e)
    {          
    }
  }

  // The top level content view.
  private ViewGroup m_contentView = null;
}

这真的有效吗?我认为只是在活动被销毁和垃圾回收之间存在一些延迟。 - ben

0

在那段代码中,内存泄漏通常发生在你旋转屏幕(即更改方向状态)时,因此你的活动被销毁并为新方向创建。关于内存泄漏有很多解释。

你可以看一下 Google I/O 2011 视频之一,了解有关内存管理的信息here。在视频中,你还可以使用可下载的 Memory Analyzer 等内存管理工具here


Setyadi,感谢您的输入。这些资源对我非常有用。 - Judy
1
内存泄漏可能发生在不旋转屏幕的情况下。 - IgorGanapolsky
@IgorGanapolsky 这就是他说“大多数”的原因。 - Denny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接