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 View
s 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 Thread
s 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 ofcontext
- 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
Android Spinner Error:Android.View.Windowmanager$Badtokenexception: Unable to Add Window
Ndk: How Include *.So Files in Androidstudio
Why Does Navigation Not Work in the Navigation Drawer Activity Template with Version 2.4.1
How to Animate a Slide in Notification View That Pushes the Content View Down
Live-Stream Video from One Android Phone to Another Over Wifi
Swap Fragment in an Activity via Animation
Multiple Table SQLite Db Adapter(S) in Android
Which View Should Be Used for New Material Design Bottom Navigation
Android: Is Using Setcontentview Multiple Times Bad While Changing Layouts
Problem About SQLite Database, No Such Table
Out of Memory Error While Loading Bitmaps
Android Ble Bluetoothgatt.Writedescriptor() Return Sometimes False
Properly Using Asynctask Get()
My App Was Rejected by Instagram Basic Display API Review Due to Invalid Reasons
Best Way to Save Large Amount of Data Locally in Unity3D Android
Why Can't One Add/Remove Items from an Arrayadapter