Handlers and Memory Leaks in Android

Can this code avoid the Android handler memory leak?

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.

Handlers and memory leaks in Android

I recently updated something similar in my own code. I just made the anonymous Handler class a protected inner class and the Lint warning went away. See if something like the below code will work for you:

public class MyGridFragment extends Fragment{

static class MyInnerHandler extends Handler{
WeakReference<MyGridFragment> mFrag;

MyInnerHandler(MyGridFragment aFragment) {
mFrag = new WeakReference<MyGridFragment>(aFragment);
}

@Override
public void handleMessage(Message message) {
MyGridFragment theFrag = mFrag.get();
switch (message.what) {
case 2:
ArrayList<HashMap<String,String>> theurls = (ArrayList<HashMap<String,String>>) message.obj;
theFrag.urls.addAll(theurls);
theFrag.theimageAdapter.notifyDataSetChanged();
theFrag.dismissBusyDialog();
break;
}//end switch
}
}
MyInnerHandler myHandler = new MyInnerHandler(this);
}

You may have to change where I put "theFrag." as I could only guess as to what those referenced.

Android - can mainThread handlers cause memory leaks?

Handler's are mainly used to post events to the Thread's MessageQueue.Each Handler instance is associated with a single thread and that thread's message queue.

so when you post a runnable with a delay, and exit from the activity, the MainThread will not be destroyed, as there are still events in the MessageQueue to be processed after a delay, so this can cause a memoryLeak as your anonymous innerclass of runnable is holding the reference of activity instance .

so make sure to remove all the messages in onStop() of Activity by calling

handler.removeCallbacksAndMessages(null);

this will clear all the pending message and callbacks before leaving your activity.

How can I resolve warnings about memory leaks when using android Handler?

Basically TextView or any other Views hold a reference of a Context object representing the corresponding Activity. When you keep a strong reference of any View in a Thread you are not only storing the View object but also a Context object representing the Activity that created it. Now as Threads are not tied with the Activity life cycle, they will continue to run even after the Activity is destroyed. If this is the case the Thread will hold a destroyed Activity's reference through that View object thus create a memory leak.

The above mentioned problem can be solved by storing a weak reference of the View object so that GC can garbage collect it when necessary. In the following way you can get rid of memory leak:

public class HandlerActivity extends AppCompatActivity implements Runnable {
WeakReference<ProgressBar> pb;
WeakReference<TextView> txtRate;
Button btnStart;
static int value;

@Override
protected void onCreate(Bundle savedInstanceState) {
...
pb = new WeakReference<>(findViewById(R.id.pb)); // you may require to cast findViewById() to ProgressBar
txtRate = new WeakReference<>(findViewById(R.id.txtRate)); // you may require to cast findViewById() to TextView
...
}

Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(pb.get()!=null) pb.get().setProgress(value);
if(txtRate.get()!=null) txtRate.get().setText("Process : " + value + "%");
}
};

@Override
public void run() {
for (int i = 1; i <= 100; i++) {
// Its always recommended to check if activity is running and stop the thread if not running
if(isFinishing() || isDestroyed()) {
return;
}
}
}
}

Can handler class with activity's context cause memory leak?

You have 2 sources of leaks in your code:

  • Using context outside the activity lifecycle. You can avoid this by using applicationContext instead of context
  • Additionally, the anonymous class you are using with handler.postDelayed holds a reference to the your activity. So you'll need to remove that in onDestroy like this:
    override fun onDestroy() {
super.onDestroy()
handlerWithContext.removeCallbacksAndMessages(null)
}

Lastly, use leakcanary to detect leaks in your code. Here's the getting started guide

How to avoid memory leaks due to custom static handler class?

The Android Studio complaining is pretty reasonable. The problem is that anonymous classes capture reference to the parent class that they were created in.

There are basically two solutions the "not pretty" and the ugly.) Both of them are about WeakReference.

#1 The not pretty solution is to make a class that will take a weak ref

class ApiRetryCallback(activity: Activity): RetryCallback(NUMBER, DELAY) {

private val weakActivity = WeakReference(activity)

override fun onRetry(retryCount: Int) {

weakActivity.get()!!.mySdkApi.applicationStartup(this) //or weakThis.get()? to swallow null cases
}

override fun onCompleted(): Boolean {
weakActivity.get()!!.updateStatus(Callback.Status.StartUpSDK)

return true
}

override fun onFailed(e: MyException?) {
weakActivity.get()!!.updateStatus(Callback.Status.StartUpSDK, "", e)
}
}

In activity:

private val startupCallback = ApiRetryCallback(this) //this is MainActivity here

#2 The ugly solution is based on a fact that lambdas should capture parent reference, only where there is a direct usage of it. So I came up with this substitution and I didn't see strong references in a debugger but you should check that:

private val startupCallback = {
val weakActivity = WeakReference(this@MainActivity)

object : RetryCallback(NUMBER, DELAY) { //returned as last expression

override fun onRetry(retryCount: Int) {

weakActivity.get()!!.mySdkApi.applicationStartup(this) //or weakThis.get()? to swallow null cases
}

//....else methods....
}

}()

Here the lambda will be called immediately and will capture only the weak reference inside the object, also it will return the last expression wich is object.

#3 While I was writing, I came up with a third solution, which is close to #2

private val startupCallback = WeakReference(this).let { //this here is MainActivity
val weakActivity = it //it of let scope wich is WeakReference

object : RetryCallback(NUMBER, DELAY) { //returned as last expression

override fun onRetry(retryCount: Int) {

weakActivity.get()!!.mySdkApi.applicationStartup(this) //or weakThis.get()? to swallow null cases
}

//....else methods....
}

}

Memory Leak in MessageQueue in android?

What is a MessageQueue?

There are 3 key Android classes tied together: Handler, Looper and MessageQueue. When a Looper instance is created, it creates its own MessageQueue instance. Then you can create a Handler et give it the Looper instance. When you call Handler.post() (or postDelayed), under the hood you're actually calling Handler.sendMessage which enqueues a Message instance on the Message queue associated to the Looper associated to that Handler.

What happens to those enqueued messages? Somewhere else in the code (i.e. dedicated HandlerThread), something calls Looper.loop() which loops forever, removing one entry at a time from the associated Message queue and running that message. If the queue is empty, the Looper waits for the next message (that's done via native code). Some more context: https://developer.squareup.com/blog/a-journey-on-the-android-main-thread-psvm/

What can we read from the LeakTrace?

We see that the MessageQueue at the top is for the following HandlerThread: "main". That's the main thread. So if you're doing a postDelayed on the main thread, a message gets enqueued into the message queue.

Messages are stored as a linked list: the MessageQueue holds on to the first message (via its mMessages field) and then each message holds on to the next.

We can see that the head of the queue is a Message and we can see its content.

Message.when = 37524601 (681 ms after heap dump)

This tells us the message was enqueued with a delay and will execute in 681ms (after the heap dump was taken)

Message.callback = instance @319985112 of com.application.app.MainActivity$$ExternalSyntheticLambda2

This tells us the callback enqueued is an inner lambda defined in MainActivity. It's hard to figure out which one, but if you decompile the bytecode (e.g. class files or dex) you should be able to tell which lambda has that name.

Fix

Most likely, you have a piece a code that keeps rescheduling itself as a postDelayed on the main thread, even after the activity is destroyed. That callback needs to be canceled in onDestroy()

Edit: note on the advice to using a WeakReference in the other answer: that's not good general advice. From the doc: https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/#4-fix-the-leak

Memory leaks cannot be fixed by replacing strong references with weak references. It’s a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.



Related Topics



Leave a reply



Submit