Избегайте утечек памяти на 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
.
Может ли кто-нибудь дать мне объяснение этому?
Ответы
Ответ 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;
}