Memory leak analysis when using handler
In Android, there are often some operations on the main thread after processing an asynchronous task, so we might use handler, the following are common uses of handler:
publicclass MainActivity extends AppCompatActivity { privatenew Handler() { @Override publicvoidhandleMessage(Message msg) { //TODO } };}
But when we use this, we see a hint:
This Handler class should is static or leaks might occur
Why is there such a hint?
In Java, an anonymous (non-static) inner class implicitly references an external object, while a static inner class does not.
So the cause of the memory leak is that activity is referenced by Mhandler , and Mhandler is strongly referenced by other objects that are longer than the activity's life cycle.
Why is handler not released? Here can be based on a simple analysis of the source code:
//activity contains a member variable Mhandler,mhandler is an instance of an anonymous inner class, let's see what's done in Handler's constructor Public Handler() { This(NULL,false); }//handler The default constructor will eventually be transferred to this method Public Handler(Callback Callback, BooleanAsync) {if(Find_potential_leaks) {final class<? extends handler> Klass = GetClass ();if((Klass.isanonymousclass () | | klass.ismemberclass () | | klass.islocalclass ()) && (klass.getmodif Iers () & modifier.static) = =0) {//It turns out there has been a test printLOG.W (TAG,"The following Handler class should be static or leaks might occur:"+ Klass.getcanonicalname ()); } }there is a mlooper in//handlerMlooper = Looper.mylooper ();if(Mlooper = =NULL) {Throw NewRuntimeException ("Can ' t create handler inside thread that have not called looper.prepare ()"); }//Message Queuing MqueueMqueue = Mlooper.mqueue; Mcallback = callback; Masynchronous =Async; }
You have seen that handler has a member variable that is mlooper, so how is this looper instantiated?
publicstaticmyLooper() { return sThreadLocal.get(); }
As you can see from the above code, the Looper instance was originally associated with the current line threads, and the Mylooper () method was called when the handler was created. Main thread.
So the member variables Mlooper and mqueue in the handler instance are at least always present during the app run. So what does this have to do with memory leaks? Must be mhandler strong reference mlooper, rather than mlooper strong reference Mhandler, why not release?
So that's what we're going to do when we use handler,postMessage ()
Below continue to see the source of the message sent in handler:
Public StaticMessageobtain(Handler h) {Message m = obtain ();//handler has been strongly referenced by message! M.target = h;returnM } Public Final Boolean sendmessagedelayed(Message msg,LongDelaymillis) {if(Delaymillis <0) {Delaymillis =0; }returnSendmessageattime (MSG, systemclock.uptimemillis () + Delaymillis); } Public Final Boolean Sendmessageatfrontofqueue(Message msg) {MessageQueue queue = Mqueue;if(Queue = =NULL) {RuntimeException E =NewRuntimeException ( This+"Sendmessageattime () called with no Mqueue"); LOG.W ("Looper", E.getmessage (), E);return false; }//will eventually be transferred to Mqueue's Enqueuemessage () method returnEnqueuemessage (Queue, MSG,0); }BooleanEnqueuemessage (Message msg,LongWhen) {//...Message p = mmessages;//... //Use a unidirectional list to save Msg.Message prev; for(;;) {prev = p; p = p.next;//has gone to the bottom of the list or the execution time of this msg is less than P's execution time to exit the loop, after which the new MSG can be inserted between the Prev node and the P node . //Ensure that the message in the MessageQueue is ordered sequentially by execution time. Where When is (Systemclock.uptimemillis () + Delaymillis) //Instead of incoming delaymillis, this is why handler can guarantee that the message order is executed. if(p = =NULL|| When < P.when) { Break; }if(Needwake && p.isasynchronous ()) {Needwake =false; }} Msg.next = P;//Invariant:p = = Prev.nextPrev.next = msg;//...}
As you can see from the above code, when we use the handler post message, the message is saved to Looper.mqueue, and when is the message destroyed? Let's see how Looper handles the message:
//activitythread.java Public Final class activitythread { Public Static void Main(string[] args) {//...Looper.loop ();//...} }//looper.java Public Static void Loop() {//Get the Looper of the main thread FinalLooper me = Mylooper ();if(Me = =NULL) {Throw NewRuntimeException ("No Looper; Looper.prepare () wasn ' t called on the This thread. "); }FinalMessageQueue queue = Me.mqueue;//... for(;;) {///Here Mqueue a strong reference to MSG, the following fine analysisMessage msg = Queue.next ();//might block if(msg = =NULL) {//No message indicates that the message queue is quitting. return; }//This must is in a local variable, with case a UI event sets the loggerPrinter logging = me.mlogging;if(Logging! =NULL) {Logging.println (">>>>> dispatching to"+ Msg.target +" "+ Msg.callback +": "+ msg.what); }//target is handler, where MSG is given to handler processingMsg.target.dispatchMessage (msg);//...Msg.recycleunchecked (); } }//message.java voidRecycleunchecked () {//Mark the message as in and the It remains in the recycled object pool. //Clear out all other details.Flags = Flag_in_use; what =0; Arg1 =0; Arg2 =0; obj =NULL; ReplyTo =NULL; Sendinguid =-1; when =0;//Not strong reference handler heretarget =NULL; callback =NULL; data =NULL;synchronized(Spoolsync) {if(Spoolsize < Max_pool_size) {//message Join Object pool waiting to be reusedNext = SPool; SPool = This; spoolsize++; } } }//messagequeue.javaMessage Next () {//... intNextpolltimeoutmillis =0; for(;;) {//Less execution time blocking, the reason that message can be executed on time if(Nextpolltimeoutmillis! =0) {binder.flushpendingcommands (); } nativepollonce (PTR, nextpolltimeoutmillis);synchronized( This) {//Try to retrieve the next message. Return if found. Final Longnow = Systemclock.uptimemillis (); Message prevmsg =NULL; Message msg = mmessages;if(msg! =NULL&& Msg.target = =NULL) {//stalled by a barrier. Find the next asynchronous message in the queue.do {prevmsg = msg; msg = Msg.next; } while(msg! =NULL&&!msg.isasynchronous ()); }if(msg! =NULL) {if(Now < Msg.when) {//Calculate MSG On-time execution requires wait time //Next message is isn't ready. Set a timeout to wake up when it was ready.Nextpolltimeoutmillis = (int) Math.min (Msg.when-now, Integer.max_value); }Else{//Got a message.mblocked =false;//message refers to Prevmsg->msg (Prevmsg.next)->msg.next, which removes msg from the one-way list by this method. //messagequeue A reference to the message instance has been lifted if(Prevmsg! =NULL) {prevmsg.next = Msg.next; }Else{mmessages = Msg.next; } Msg.next =NULL;if(DEBUG) LOG.V (TAG,"Returning message:"+ msg); Msg.markinuse ();returnMsg } }Else{//No more messages.Nextpolltimeoutmillis =-1; }//... //So go back and look again for a pending message without waiting.Nextpolltimeoutmillis =0; } }
The key position in the source code has been annotated, through the above analysis, we can finally get a complete memory leak reference path:
Activity <-handler <-Message <-messagequeue <-looper <-Runtime
The activity cannot be freed as long as there is a message that is sent out, so we should know how to modify it:
- To remove a reference between activity and handler:
PrivateSafehandler Mhandler =NewSafehandler ( This);Private Static class safehandler extends Handler{Weakreference<mainactivity> mactivityreference; Public Safehandler(mainactivity activity) {mactivityreference =NewWeakreference<mainactivity> (activity); }@Override Public void Handlemessage(Message msg) {Mainactivity activity = mactivityreference.get ();if(Activity! =NULL) {activity.handlemessage (msg); } } }Private void Handlemessage(Message msg) {//todo}
- To dismiss a reference between handler and message
mHandler.removeCallbacksAndMessages(null);
Android message processing mechanism
According to the above source code Analysis We also clarify the message, Handler, MessageQueue and looper the collaboration between the four mechanisms:
Message:
Purpose: carry the task information to be sent to the handler thread.
Directly referenced location: MessageQueue
Handler:
Purpose: is responsible for wrapping the task information into a message or adding the message instance directly to MessageQueue (any thread), and also responsible for processing the message passed by Looper on the thread (target thread).
Direct referenced location: Any object can contain handler,handler to establish a binding relationship by referencing the looper stored on the thread.
MessageQueue:
Purpose: MessageQueue refers to all messages to be sent, holds a message instance reference, inserts each new message into a one-way list, and is not saved by creating a structure such as a ArrayList.
Direct referenced location: A MessageQueue object is initialized when each looper is instantiated.
Looper:
Usage: Responsible for adding MessageQueue message to handler for processing, to complete the processing of messages.
Directly referenced location: thread's threadlocal (threaded local variable), not as a member variable of thread.
Here is a figure analogy (in fact, not very good, no real >.>):
Team: Thread
Coal: Message
Belt: MessageQueue
Team Coal worker: Handler
Belt Conveyor: Looper
The coal workers of any team can throw coal (any thread to send a message), and throw it to the belt belt to arrange it in a certain order (MessageQueue is a message queue), then the transport belt will be driven by one of its handling (Looper.loop ()) , each coal is marked with a coal worker (a message referencing handler), and the belt conveyor is transported by Mark to the coal heap of the individual coal workers ' corresponding team (Looper sends the message to the corresponding handler handlemessage () processing), Of course, there is only one pile that can be seen as a coal worker with only one team.
Done.
Source tracking handler working mechanism from the angle of memory leakage using handler