4

handler1 is a leak.

I want to convert handler1 code to handler2 code. Is that OK?

What is the difference between the two codes?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // leaks!
        Handler handler1 = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.e("LOG", "Hello~1");
            }
        };

        Handler handler2 = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.e("LOG", "Hello~2");
                return false;
            }
        });

        handler1.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60000);
        handler2.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60000);

        finish();
    }
}
samabcde
  • 6,988
  • 2
  • 25
  • 41
user2324746
  • 53
  • 1
  • 5

3 Answers3

8

Why Leak Warning for handler1?

For the reason on leak warning, this article explains very well.

Quoting from the article

In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.

So when you created handler1 by anonymous class, it will holds a reference to the MainActivity instance and MainActiviy can not be garbage collected.

Solution

Quoting from the article again

To fix the problem, subclass the Handler in a new file or use a static inner class instead. Static inner classes do not hold an implicit reference to their outer class, so the activity will not be leaked. If you need to invoke the outer activity’s methods from within the Handler, have the Handler hold a WeakReference to the activity so you don’t accidentally leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not hold an implicit reference to their outer class):

Following the article, update your code as follows:

public class MainActivity extends AppCompatActivity {

    private static class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
             Log.e("LOG", "Hello~1");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Handler handler1 = new MyHandler();

        handler1.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60000);

        finish();
    }
}

Can handler2 solve the problem?

The answer from @Michael This Handler class should be static or leaks might occur: IncomingHandler provides the solution.

Quoting from @Michael answer

As I understand it, this will not avoid the potential memory leak. Message objects hold a reference to the mIncomingHandler object which holds a reference the Handler.Callback object which holds a reference to the Service object. As long as there are messages in the Looper message queue, the Service will not be GC. However, it won't be a serious issue unless you have long delay messages in the message queue.

In your case, handler2 will hold a reference to Handler.Callback object.And since Handler.Callback is created by anonymous class, hence it will hold a reference to MainActiviy instance too. So MainActiviy instance can not be garbage collected also.

samabcde
  • 6,988
  • 2
  • 25
  • 41
1

Android Studio Screenshot

I try this code on emulator(android 28) and dump the memory, the profiler shows nothing to leak, I also try Leakcanary and shows same result. This makes me doubt the accuracy of the articles on the web. Then I noticed the difference,if I use Kotlin write this logic, the memory will not leak, but use the Java code will leak.

Later I found something interesting. In java, whether use outer class method or not,the anonymous inner class will hold outer object reference by constructor. In kotlin,if inner logic is not use outer class method,the anonymous inner class dose not hold the outer object reference.

Bill Lv
  • 71
  • 1
  • 3
0

I used Handler in Kotlin code that catches the incoming Bluetooth incoming message. This code has no lint leaks warnings:

private val incomingMsgHandler: Handler = Handler { msg ->
    msg.obj?.let {
        if (it is ByteArray) {
            val msgStr = String(it)
            setIncomingMessage(msgStr)
        }
    }
    true
}

In short description, this uses Handler constructor with lambda callback.

MeLean
  • 3,092
  • 6
  • 29
  • 43