Подтвердить что ты не робот

Избегайте утечек памяти на Android

Я просто прочитал блог-почту от Ромена Гая о том, как избежать утечек памяти в 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);
}

Ромен сказал:

Этот пример является одним из самых простых случаев утечки Контекста.

Мой вопрос в том, как вы правильно его изменяете?

Как это?

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

Я тестировал оба пути, и результаты были одинаковыми. Я не могу найти разницу. И я думаю, что this более корректен, чем контекст приложения. Поскольку this является ссылкой на Activity, т.е. TextView принадлежит этому Activity.

Может ли кто-нибудь дать мне объяснение этому?

4b9b3361

Ответ 1

Фактическая проблема с этим кодом - это не контекст, переданный для создания выталкиваемого, но частного статического Drawable sBackground; Статический Drawable создается с помощью Activity как контекста, поэтому в случае THAT существует статическая ссылка на Drawable, которая ссылается на Activity, и на то, что там происходит утечка. Пока эта ссылка существует, активность будет храниться в памяти, утечка всех ее представлений.

Итак, это Drawable, который должен быть создан с использованием контекста приложения, а не TextView. Создание TextView с помощью "this" отлично.

edit: На самом деле это может не иметь большого значения, проблема в том, что как только drawable привязан к представлению, есть ссылка на представление, которое ссылается на действие. Таким образом, вам нужно "отвязать" выталкиваемый при выходе из операции.

Ответ 2

Я не уверен, обновил ли Romain свою запись в блоге, так как вы ее прочитали, но он довольно четко понимает, как избежать утечек, даже указывая на пример в ОС Android. Обратите внимание, что я исправил неработающую ссылку в записи блога Romain через archive.org.

Этот пример является одним из самых простых случаев утечки Контекста и вы можете увидеть, как мы работали вокруг него в Начальный экран источника код (найдите метод unbindDrawables()), установив сохраненный обратные вызовы обратных вызовов до нуля, когда действие уничтожается. Интересно, что бывают случаи, когда вы можете создать цепочку утечка контекстов, и они плохие. У вас заканчивается память довольно быстро.

Существует два простых способа избежать утечек памяти, связанных с контекстом. наиболее очевидным является избежание выхода из контекста вне его собственных объем. В приведенном выше примере показан случай статической ссылки, но внутренние классы и их неявная ссылка на внешний класс могут быть одинаково опасно. Второе решение - использовать приложение контекст. Этот контекст будет жить до тех пор, пока ваше приложение будет живым и не зависит от жизненного цикла деятельности. Если вы планируете сохраняя долгоживущие объекты, которые нуждаются в контексте, помните объект приложения. Вы можете легко получить его, позвонив Context.getApplicationContext() или Activity.getApplication().

Таким образом, чтобы избежать утечек памяти, связанных с контекстом, помните следующее:

  • Не сохраняйте долгосрочные ссылки на контекст-активность (ссылка на деятельность должна иметь тот же жизненный цикл, что и самой деятельности)
  • Попробуйте использовать контекстное приложение вместо контекстной активности
  • Избегайте нестатических внутренних классов в действии, если вы не контролируете их жизненный цикл, используйте статический внутренний класс и слабо ссылаетесь на активность внутри. Решение этой проблемы заключается в использовании статического внутреннего класса с WeakReference для внешнего класса, как это сделано в ViewRoot и его внутреннем классе W, например
  • Сборщик мусора не является страхованием от утечек памяти.

Ответ 3

Утечки памяти в этом коде чаще всего происходят при повороте экрана (т.е. изменении состояния ориентации), поэтому ваша активность была уничтожена и снова создана для новой ориентации. Там много объяснений о утечке памяти.

Вы можете посмотреть один из видеороликов Google I/O 2011 об управлении памятью здесь. В видео вы также можете использовать инструменты управления памятью, такие как Memory Analyzer, для загрузки здесь.

Ответ 4

Я не знаю, есть ли у вас какие-либо проблемы с этим в вашем приложении, но я создал решение, которое устраняет все проблемы с утечкой памяти Android со стандартными классами 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;
}