Используем static Handler
Используем static Handler
В уроках используется обычный Handler. Eclipse на это ругается: "This Handler class should be static or leaks might occur " ("Класс Handler должен быть static, иначе могут быть утечки памяти") и он абсолютно прав. Пофиксим это.
Приведу пример static Handler.
[syntax=java5]public class MainActivity extends Activity {
Handler handler;
TextView tvTest;
int cnt = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvTest = (TextView) findViewById(R.id.tvTest);
handler = new MyHandler(this);
handler.sendEmptyMessageDelayed(0, 1000);
}
void someMethod() {
tvTest.setText("Count = " + cnt++);
handler.sendEmptyMessageDelayed(0, 1000);
}
@Override
protected void onDestroy() {
if (handler != null)
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
static class MyHandler extends Handler {
WeakReference<MainActivity> wrActivity;
public MyHandler(MainActivity activity) {
wrActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = wrActivity.get();
if (activity != null)
activity.someMethod();
}
}
}[/syntax]
Код в целом несложен: на экране просто идет счетчик, который работает через отложенные сообщения Handler.
В чем разница между обычным Handler и static? Чтобы понять это, необходимо знать две вещи:
1) Не static Handler содержит скрытую ссылку на Activity.
2) Сообщения предназначенные для Handler содержат ссылку на сам Handler (а следовательно и на Activity).
Два этих факта могут вызвать утечку памяти. Например, вы в системную очередь на выполнение помещаете (с помощью Handler) сообщение, которое должно выполниться через час. Затем закрываете приложение. Система хочет освободить память и поудалять из нее неиспользуемые объекты. И она удалила бы закрытое Activity и все его содержимое, но не может. Потому что сообщение, которое будет висеть еще час, хранит ссылку на Handler, а Handler хранит ссылку на Activity. В итоге одно сообщение (которое, скорее всего, уже никому не нужно) держит зря целое Activity и не дает системе удалить его из памяти.
Объявляя Handler как static, мы разрубаем цепь ссылок и Handler больше не хранит ссылку на Activity. Но! Handler обычно используется чтобы взаимодействовать с UI, и он должен уметь работать с Activity и его методами. А для этого он должен иметь ссылку на Activity.
Получается легкое противоречие. Handler должен хранить ссылку на Activity, чтобы работать с ним. Но не должен хранить ссылку на Activity, чтобы не вызвать утечку памяти.
Тут выручает Java-механизм слабых ссылок - WeakReference. Слабая ссылка не учитывается системой при очистке памяти. Если система видит, что Activity больше не нужно, но есть Handler, который хранит слабую ссылку на Activity, - слабая ссылка будет проигнорена, Activity будет удалено и утечек памяти не будет.
Мы в конструктор Handler-а передаем Activity и для хранения используем контейнер WeakReference. Метод get позволяет нам получить ссылку обратно из контейнера. А если WeakReference возвращает null, значит объект был удален из памяти, т.е. Activity было закрыто.
Это очень полезный механизм и применим не только к Handler. Рекомендую понять его и использовать для предотвращения утечек.
Приведу пример static Handler.
[syntax=java5]public class MainActivity extends Activity {
Handler handler;
TextView tvTest;
int cnt = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvTest = (TextView) findViewById(R.id.tvTest);
handler = new MyHandler(this);
handler.sendEmptyMessageDelayed(0, 1000);
}
void someMethod() {
tvTest.setText("Count = " + cnt++);
handler.sendEmptyMessageDelayed(0, 1000);
}
@Override
protected void onDestroy() {
if (handler != null)
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
static class MyHandler extends Handler {
WeakReference<MainActivity> wrActivity;
public MyHandler(MainActivity activity) {
wrActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = wrActivity.get();
if (activity != null)
activity.someMethod();
}
}
}[/syntax]
Код в целом несложен: на экране просто идет счетчик, который работает через отложенные сообщения Handler.
В чем разница между обычным Handler и static? Чтобы понять это, необходимо знать две вещи:
1) Не static Handler содержит скрытую ссылку на Activity.
2) Сообщения предназначенные для Handler содержат ссылку на сам Handler (а следовательно и на Activity).
Два этих факта могут вызвать утечку памяти. Например, вы в системную очередь на выполнение помещаете (с помощью Handler) сообщение, которое должно выполниться через час. Затем закрываете приложение. Система хочет освободить память и поудалять из нее неиспользуемые объекты. И она удалила бы закрытое Activity и все его содержимое, но не может. Потому что сообщение, которое будет висеть еще час, хранит ссылку на Handler, а Handler хранит ссылку на Activity. В итоге одно сообщение (которое, скорее всего, уже никому не нужно) держит зря целое Activity и не дает системе удалить его из памяти.
Объявляя Handler как static, мы разрубаем цепь ссылок и Handler больше не хранит ссылку на Activity. Но! Handler обычно используется чтобы взаимодействовать с UI, и он должен уметь работать с Activity и его методами. А для этого он должен иметь ссылку на Activity.
Получается легкое противоречие. Handler должен хранить ссылку на Activity, чтобы работать с ним. Но не должен хранить ссылку на Activity, чтобы не вызвать утечку памяти.
Тут выручает Java-механизм слабых ссылок - WeakReference. Слабая ссылка не учитывается системой при очистке памяти. Если система видит, что Activity больше не нужно, но есть Handler, который хранит слабую ссылку на Activity, - слабая ссылка будет проигнорена, Activity будет удалено и утечек памяти не будет.
Мы в конструктор Handler-а передаем Activity и для хранения используем контейнер WeakReference. Метод get позволяет нам получить ссылку обратно из контейнера. А если WeakReference возвращает null, значит объект был удален из памяти, т.е. Activity было закрыто.
Это очень полезный механизм и применим не только к Handler. Рекомендую понять его и использовать для предотвращения утечек.
Re: Используем static Handler
очень полезно. спасибо. вы меня спровоцировали почитать о рефлексии
Re: Используем static Handler
А как восстановить ссылку в MyHandler на MainActivity?
Например, при смене ориентации экрана, Activity пересоздается, а используя wrActivity.get() в MyHandler мы будем ссылаться на null или старый Activity.
А как найти ссылку на вновь созданный MainActivity?
Например, при смене ориентации экрана, Activity пересоздается, а используя wrActivity.get() в MyHandler мы будем ссылаться на null или старый Activity.
А как найти ссылку на вновь созданный MainActivity?
Re: Используем static Handler
не должно быть так.Sikambr писал(а):А как восстановить ссылку в MyHandler на MainActivity?
Например, при смене ориентации экрана, Activity пересоздается, а используя wrActivity.get() в MyHandler мы будем ссылаться на null или старый Activity.
А как найти ссылку на вновь созданный MainActivity?
после переворота создается новый MyHandler в onCreate():
[syntax=java]handler = new MyHandler(this);[/syntax]
Re: Используем static Handler
Вы правы!klblk писал(а):не должно быть так.
Но только не по причине
а вот из за этогоklblk писал(а):после переворота создается новый MyHandler в onCreate():
handler = new MyHandler(this);
Код: Выделить всё
if (handler != null)
handler.removeCallbacksAndMessages(null);
Код: Выделить всё
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "text", Message.obtain(new MyHandler());
Единственное, что в голову лезет, так это то, что MainActivity может быть только один, вот его как-то и найти.
Re: Используем static Handler
У меня обратный вопрос - как сохранить состояние MainActivity и хэндлера при перевороте экрана ?
На примере того же счётчика, т.е. чтобы работа счётчика не прерывалась.
Или же такой подход будет неверным, и функцию счётчика следует возложить на тот же сервис, а из MainActivity при пересоздании - обращаться к сервису ?
Или же всё-таки можно сделать, например через свою реализацию onSaveInstanceState ?
Вопрос именно в идеологии.
На примере того же счётчика, т.е. чтобы работа счётчика не прерывалась.
Или же такой подход будет неверным, и функцию счётчика следует возложить на тот же сервис, а из MainActivity при пересоздании - обращаться к сервису ?
Или же всё-таки можно сделать, например через свою реализацию onSaveInstanceState ?
Вопрос именно в идеологии.
Re: Используем static Handler
Доброго всем дня.
Я реализую Handler в созданном мной классе. Применрно так:
Но ведь в таком случае метод processMessage() становится доступным любому, кто создал эксземпляр класса MyClass(). Как от этого защититься?
Я реализую Handler в созданном мной классе. Применрно так:
Код: Выделить всё
public class MyClass
{
private void someMethod1()
{
}
private void someMethod2()
{
}
void processMessage(Message msg)
{
switch (msg.what)
{
case myclass.MSG1:
{
...
someMethod1();
break;
}
case myclass.MSG2:
{
...
someMethod2();
break;
}
}
}
private final int MSG1 = 1;
private final int MSG2 = 2;
private final int MSG3 = 3;
static class wHandler extends Handler
{
WeakReference<MyClass> wrMyClass;
public wHandler(MyClass myclass)
{
wrWheel = new WeakReference<MyClass>(myclass);
}
@Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
MyClass myclass = wrMyClass.get();
myclass.processMessage(msg);
}
}
}
Re: Используем static Handler
private void processMessage(Message msg)?
Re: Используем static Handler
Да, конечно. Спасибо. Надо же мне было так ступить...klblk писал(а):private void processMessage(Message msg)?
Re: Используем static Handler
У меня тоже вопрос возник по Handler'у. Что если бы его будем создавать не в onCreate, а сразу инициализируем его:
[syntax=java]public class MainActivity extends Activity {
Handler handler = new MyHandler(this);
...
}[/syntax]
Будут ли в данном случае неприятные последствия? Насколько я помню Java в данном случае он создастся в конструкторе MainActivity.
[syntax=java]public class MainActivity extends Activity {
Handler handler = new MyHandler(this);
...
}[/syntax]
Будут ли в данном случае неприятные последствия? Насколько я помню Java в данном случае он создастся в конструкторе MainActivity.
Re: Используем static Handler
http://stackoverflow.com/questions/2079 ... ng-handlerklblk писал(а):У меня тоже вопрос возник по Handler'у. Что если бы его будем создавать не в onCreate, а сразу инициализируем его:
[syntax=java]public class MainActivity extends Activity {
Handler handler = new MyHandler(this);
...
}[/syntax]
Будут ли в данном случае неприятные последствия? Насколько я помню Java в данном случае он создастся в конструкторе MainActivity.
Семь раз отмерь - поставь студию.
Эклипс не студия, ошибка вылетит - не исправишь.
Скажи мне кто твой друг, и оба поставили студию.
Студия - свет, а эклипс - тьма.
Эклипс не студия, ошибка вылетит - не исправишь.
Скажи мне кто твой друг, и оба поставили студию.
Студия - свет, а эклипс - тьма.
Re: Используем static Handler
Жаль, что вы не привели пример кода с использованием мягких ссылок.
На всякий случай приведу код урока, который у меня получился в итоге.
На всякий случай приведу код урока, который у меня получился в итоге.
Код: Выделить всё
package ru.mytest.p0801_handler;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
public class MainActivity extends ActionBarActivity {
final String LOG_TAG = "myLog";
TextView tvInfo;
Button btnStart;
static myHandler mHandler;
private static class myHandler extends Handler {
private WeakReference<MainActivity> mTarget;
myHandler(MainActivity target) {
mTarget = new WeakReference<MainActivity>(target);
}
public void setTarget(MainActivity target) {
mTarget.clear();
mTarget = new WeakReference<MainActivity>(target);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mTarget.get();
// обновляем TextView
activity.tvInfo.setText("Закачано файлов: " + msg.what);
if (msg.what == 10) activity.btnStart.setEnabled(true);
}
}
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvInfo = (TextView) findViewById(R.id.tvInfo);
btnStart = (Button) findViewById(R.id.btnStart);
if(mHandler == null) mHandler = new myHandler(this);
else mHandler.setTarget(this);
}
public void onclick(View v) {
switch (v.getId()) {
case R.id.btnStart:
btnStart.setEnabled(false);
Thread t = new Thread(new Runnable() {
public void run() {
for (int i = 1; i <= 10; i++) {
// долгий процесс
downloadFile();
mHandler.sendEmptyMessage(i);
// пишем лог
Log.d(LOG_TAG, "i = " + i);
}
}
});
t.start();
break;
case R.id.btnTest:
Log.d(LOG_TAG, "test");
break;
default:
break;
}
}
void downloadFile() {
// пауза - 1 секунда
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- Mikhail_dev
- Сообщения: 2386
- Зарегистрирован: 09 янв 2012, 14:45
- Откуда: Самара
Re: Используем static Handler
Мягкие ссылки? Вы серьёзно? Тут народ кнопочку боится в XML написать, а базовую Java знает процентов 30 от силы, а тут мягкие ссылки. К сожалению (хотя наверное к счастью =) ), их никто особо и не знает. Об этой теме я даже заметку написал, в подписи. Первая.
Re: Используем static Handler
Обхожу это так:"This Handler class should be static or leaks might occur "
Код: Выделить всё
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG) {
// your code
return true;
}
return false;
}
});
Русскоязычный чат Android разработчиков https://gitter.im/rus-speaking/android
-
- Сообщения: 36
- Зарегистрирован: 28 июн 2015, 03:13
Re: Используем static Handler
Спасибо. Ваш код очень помог.onesoft писал(а):Жаль, что вы не привели пример кода с использованием мягких ссылок.
На всякий случай приведу код урока, который у меня получился в итоге.Код: Выделить всё
package ru.mytest.p0801_handler; import android.os.Handler; import android.os.Message; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; public class MainActivity extends ActionBarActivity { final String LOG_TAG = "myLog"; TextView tvInfo; Button btnStart; static myHandler mHandler; private static class myHandler extends Handler { private WeakReference<MainActivity> mTarget; myHandler(MainActivity target) { mTarget = new WeakReference<MainActivity>(target); } public void setTarget(MainActivity target) { mTarget.clear(); mTarget = new WeakReference<MainActivity>(target); } @Override public void handleMessage(Message msg) { MainActivity activity = mTarget.get(); // обновляем TextView activity.tvInfo.setText("Закачано файлов: " + msg.what); if (msg.what == 10) activity.btnStart.setEnabled(true); } } /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tvInfo = (TextView) findViewById(R.id.tvInfo); btnStart = (Button) findViewById(R.id.btnStart); if(mHandler == null) mHandler = new myHandler(this); else mHandler.setTarget(this); } public void onclick(View v) { switch (v.getId()) { case R.id.btnStart: btnStart.setEnabled(false); Thread t = new Thread(new Runnable() { public void run() { for (int i = 1; i <= 10; i++) { // долгий процесс downloadFile(); mHandler.sendEmptyMessage(i); // пишем лог Log.d(LOG_TAG, "i = " + i); } } }); t.start(); break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; } } void downloadFile() { // пауза - 1 секунда try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
Но не совсем понятно:
1. В каком случае произойдёт вызов mHandler.setTarget(this); ??? Может ли вообще такое случиться и в какой ситуации?
Код: Выделить всё
if(mHandler == null) mHandler = new myHandler(this);
else mHandler.setTarget(this);
Нужно ли дописать его в данном случае?@Override
protected void onDestroy() {
if (handler != null)
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
Я так полагаю, что метод handler.removeCallbacksAndMessages(null); позволяет нам удалять только сообщения, но это не позволяет решить всю проблему утечки памяти, т.к. ссылки на объекты остаются??
Re: Используем static Handler
Друзья, извините, если вопрос глупый. Пытаюсь работать с потоками из фрагмента. Нужны ли в таком случае слабые ссылки? Если да, то нужно ли передавать в статический handler Activity (и если тоже "да", то как?).
Заранее спасибо.
Заранее спасибо.
Re: Используем static Handler
Мы удалили все сообщения из очереди в хендлере - таким образом хендлер готов чтобы его очистил GC.K_Vladimir писал(а): 2. Здесь на форуме, в главном примере, есть переопределение метода onDestroy:Нужно ли дописать его в данном случае?@Override
protected void onDestroy() {
if (handler != null)
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
Я так полагаю, что метод handler.removeCallbacksAndMessages(null); позволяет нам удалять только сообщения, но это не позволяет решить всю проблему утечки памяти, т.к. ссылки на объекты остаются??
Если хендлер создавать в статическом внутреннем классе или в отдельном классе тогда он не имеет ссылки на Активити, а значит не будет удерживать его от очистки GC.
Re: Используем static Handler
итак я для себя выделил такие утечки:
1. Статическая переменная класса Activity содержит в себе ссылку на контекст.
Пример: http://android-developers.blogspot.ru/2 ... leaks.html Допустим у вас в onCreate() создается некий Drawable, который достаточно большой и вы хотите чтобы в случае смены ориентации экрана (при смене вызывается onDestroy() и onCreate() каждый раз) его не создавать заново, а взять из статической переменной класса. Все ок, но если вы связали этот Drawable например с каким-то View, то в статическом поле будет сидеть drawable c уже ссылкой на весь Активити, т.к. в каждом View есть ссылка на Контекст. И этот весь старый Активити сборщик мусора не уберет.
Решение: http://stackoverflow.com/questions/6567 ... on-android Решается это тем, что в методе оnDestroy делается unbind, то есть обнуляется связь у Drawable, чтобы он не тянул за собой весь предыдущий активити.
2. Утечка в случае с хендлером. Если хендлер объявить как внутренний класс в Активити, то может образоваться утечка, т.к. внутренний класс содержит скрытую ссылку на внешний класс (Активити) по этому Активити не очистится GC*, а так как в Хендлере будут сообщения которые ожидают своей отработки (а они не могут потому что Активити давно уже закрыта) то и Хендлер не очистится GC.
Пример и решение здесь: http://www.androiddesignpatterns.com/20 ... -leak.html Если коротко то, надо хендлер инициализировать как вложенный статический класс или как отдельный класс. А если необходимо в него передать все таки ссылку на контекст - передать ее через класс-переходник WeakReference что позволит удалить Активити даже при наличии слабой ссылки.
3. внутренние классы - все то же самое как и в п. 2 (внутренние классы содержат ссылки на внешние классы)
Здесь как еще один пример могут послужить анонимные внутренние классы ...new Runnable(){... или new Thread(){ они тоже содержат ссылку на внешний класс и если этот поток явно не потушить то он будет держать Активити пока ОС не вырубит все приложение.
http://www.androiddesignpatterns.com/20 ... leaks.html Здесь в статье показывается на диаграмме как забивается память при таких открываниях потока если десять раз поменять ориентацию экрана (покрутить телефон).
Решение: в этой же статье. Если коротко - определйте свои методы Thread и устанавливайте метод close() котторый бы убивал этот поток. В методе onDestroy() вызывайе myThread.close();
Единственное что мне здесь не понятно: в статье сказано что
After each configuration change, the Android system creates a new Activity and leaves the old one behind to be garbage collected. However, the thread holds an implicit reference to the old Activity and prevents it from ever being reclaimed. As a result, each new Activity is leaked and all resources associated with them are never able to be reclaimed. то есть каждый раз создается новое Активити, а старые из-за скрытой ссылки никогда уже не будут очищенны (разумеется пока работает приложение).
Так вот вопрос: когда поток отработает - он должен сам обнулиться, а значит и ссылка на Активити пропадет - следовательно Активити сможет быть очищен GC, так ли это или уже все, поток завис и не закончится никогда, а значит и Активити будет висеть до последнего?
* - GC - Garbage Collection ( http://www.oracle.com/webfolder/technet ... index.html )
1. Статическая переменная класса Activity содержит в себе ссылку на контекст.
Пример: http://android-developers.blogspot.ru/2 ... leaks.html Допустим у вас в onCreate() создается некий Drawable, который достаточно большой и вы хотите чтобы в случае смены ориентации экрана (при смене вызывается onDestroy() и onCreate() каждый раз) его не создавать заново, а взять из статической переменной класса. Все ок, но если вы связали этот Drawable например с каким-то View, то в статическом поле будет сидеть drawable c уже ссылкой на весь Активити, т.к. в каждом View есть ссылка на Контекст. И этот весь старый Активити сборщик мусора не уберет.
Решение: http://stackoverflow.com/questions/6567 ... on-android Решается это тем, что в методе оnDestroy делается unbind, то есть обнуляется связь у Drawable, чтобы он не тянул за собой весь предыдущий активити.
2. Утечка в случае с хендлером. Если хендлер объявить как внутренний класс в Активити, то может образоваться утечка, т.к. внутренний класс содержит скрытую ссылку на внешний класс (Активити) по этому Активити не очистится GC*, а так как в Хендлере будут сообщения которые ожидают своей отработки (а они не могут потому что Активити давно уже закрыта) то и Хендлер не очистится GC.
Пример и решение здесь: http://www.androiddesignpatterns.com/20 ... -leak.html Если коротко то, надо хендлер инициализировать как вложенный статический класс или как отдельный класс. А если необходимо в него передать все таки ссылку на контекст - передать ее через класс-переходник WeakReference что позволит удалить Активити даже при наличии слабой ссылки.
3. внутренние классы - все то же самое как и в п. 2 (внутренние классы содержат ссылки на внешние классы)
Здесь как еще один пример могут послужить анонимные внутренние классы ...new Runnable(){... или new Thread(){ они тоже содержат ссылку на внешний класс и если этот поток явно не потушить то он будет держать Активити пока ОС не вырубит все приложение.
http://www.androiddesignpatterns.com/20 ... leaks.html Здесь в статье показывается на диаграмме как забивается память при таких открываниях потока если десять раз поменять ориентацию экрана (покрутить телефон).
Решение: в этой же статье. Если коротко - определйте свои методы Thread и устанавливайте метод close() котторый бы убивал этот поток. В методе onDestroy() вызывайе myThread.close();
Единственное что мне здесь не понятно: в статье сказано что
After each configuration change, the Android system creates a new Activity and leaves the old one behind to be garbage collected. However, the thread holds an implicit reference to the old Activity and prevents it from ever being reclaimed. As a result, each new Activity is leaked and all resources associated with them are never able to be reclaimed. то есть каждый раз создается новое Активити, а старые из-за скрытой ссылки никогда уже не будут очищенны (разумеется пока работает приложение).
Так вот вопрос: когда поток отработает - он должен сам обнулиться, а значит и ссылка на Активити пропадет - следовательно Активити сможет быть очищен GC, так ли это или уже все, поток завис и не закончится никогда, а значит и Активити будет висеть до последнего?
* - GC - Garbage Collection ( http://www.oracle.com/webfolder/technet ... index.html )
-
- Сообщения: 1
- Зарегистрирован: 10 окт 2017, 14:05
Re: Используем static Handler
любой не статический внутренний класс (в том числе анонимный) содержит неявную ссылку на объект-энклозинг. В исходниках соотв. примера создаются объекты двух анонимных классов - хендлер и ранабл для потока и оба содержат неявную сильную ссылку на активю. Когда поток закончит исполнение, он релизнет ссылку на свой ранабл и, как следствие, на активю.
Мораль - лики возникают тогда, когда объект с бОльшим скоупом ссылается на объект с мЕньшим скоупом, а уж как это будет имплементировано - через статику, анонимные классы, статические внутренние с незачищенным листнером - дело десятое. Строго говоря, исходник из примера все равно может ликать активю, но не через хэндлер, а через ранабл
Мораль - лики возникают тогда, когда объект с бОльшим скоупом ссылается на объект с мЕньшим скоупом, а уж как это будет имплементировано - через статику, анонимные классы, статические внутренние с незачищенным листнером - дело десятое. Строго говоря, исходник из примера все равно может ликать активю, но не через хэндлер, а через ранабл