Reproduced "Android Handler, Message"

Source: Internet
Author: User
Tags message queue

Before also because weekend night watching TI3 competition, has not found the time to write a blog, resulting in a long period of not updated. Ashamed! Back to the progress of the next, try to make sure to write every week. Here is also the first to congratulate the Alliance team from Sweden won the TI3 champion, I hope next year China team can tiger up!

Getting started, we all know that the Android UI is thread insecure, and if you try to do UI action in a child thread, the program might crash. I believe that everyone in the daily work will often encounter this problem, the solution should also have been familiar with the heart, that is, create a Message object, and then send out with the help of handler, after the handler handlemessage () method to get the message object you just sent, and then the UI action here will no longer crash.

This processing is called an asynchronous message processing thread, although I believe everyone will use it, but do you know what the rationale behind it is? Today we come together to delve into the secrets behind handler and message.

First look at how to create a handler object. You may find it quite puzzling to create a handler what's so nice about it, just a new one, right? Yes, but even if it's just a simple new one, there's still a lot of room to be aware of, and we're trying to create two handler objects in the program that are created in the main thread, one created in the child thread, and the code looks like this:

public class Mainactivity extends Activity {private Handler handler1;private Handler handler2; @Overrideprotected void OnC Reate (Bundle savedinstancestate) {super.oncreate (savedinstancestate); Setcontentview (R.layout.activity_main); Handler1 = new Handler (); New Thread (New Runnable () {@Overridepublic void run () {handler2 = new Handler ();}}). Start ();}}

If you run the program now, you will find that the handler created in the sub-thread will cause the program to crash, and the error message for the can ' t create handler inside thread, the have not called Looper.prepare ( ) 。 It is not possible to create a handler in a thread that does not call Looper.prepare (), so we try to call Looper.prepare () in the child thread first, and the code is as follows:

New Thread (New Runnable () {@Overridepublic void run () {looper.prepare (); handler2 = new Handler ()}). Start ();

Sure enough this will not collapse, but only satisfied with this is obviously not enough, we look at the source of handler, figure out why not call Looper.prepare () will not do it. The parameterless constructor for handler is as follows:

Public Handler () {    if (find_potential_leaks) {        final class<? extends handler> Klass = GetClass ();        if (Klass.isanonymousclass () | | klass.ismemberclass () | | klass.islocalclass ()) &&                (Klass.getmodifiers () & modifier.static) = = 0) {            log.w (TAG, "the following Handler class should be STATIC or leaks might occur:" +                Klass.getcanonicalname ());        }    }    Mlooper = Looper.mylooper ();    if (Mlooper = = null) {        throw new RuntimeException (            "Can ' t create handler inside thread that have not called Loope R.prepare () ");    }    Mqueue = Mlooper.mqueue;    Mcallback = null;}

As you can see, the Looper.mylooper () method called in line 10th gets a Looper object, and if the Looper object is empty, it throws a run-time exception, prompting for the error just can ' t create handler inside thread that have not called looper.prepare ()! When will the Looper object be empty? This will take a look at the code in Looper.mylooper (), as follows:

public static final Looper Mylooper () {    return (Looper) sthreadlocal.get ();

This method is very simple to remove the Looper from the Sthreadlocal object. If there is looper presence in the sthreadlocal, return to Looper, if there is no looper there is a natural return empty. So you can imagine where to put the sthreadlocal set Looper, of course, is the Looper.prepare () Method! Let's look at its source code:

public static final void prepare () {    if (sthreadlocal.get () = null) {        throw new RuntimeException ("Only one Looper May is created per thread ");    }    Sthreadlocal.set (New Looper ());}

As you can see, first determine if there is already a looper in sthreadlocal, and if not, create a new Looper setting in. This also completely explains why we need to call the Looper.prepare () method before we can create a handler object. You can also see that there is a maximum of only one Looper object per thread.

Hey? Oh, no! The handler in the main thread also didn't call the Looper.prepare () method, why didn't it crash? Careful friend I believe that has already found this, this is because when the program starts, the system has helped us to automatically call the Looper.prepare () method. To view the main () method in Activitythread, the code looks like this:

public static void Main (string[] args) {    samplingprofilerintegration.start ();    Closeguard.setenabled (false);    Environment.initforcurrentuser ();    Eventlogger.setreporter (New Eventloggingreporter ());    Process.setargv0 ("<pre-initialized>");    Looper.preparemainlooper ();    Activitythread thread = new Activitythread ();    Thread.attach (false);    if (Smainthreadhandler = = null) {        Smainthreadhandler = Thread.gethandler ();    }    Asynctask.init ();    if (false) {        looper.mylooper (). setmessagelogging (New Logprinter (Log.debug, "Activitythread"));    }    Looper.loop ();    throw new RuntimeException ("Main thread loop unexpectedly exited");}

As you can see, the Looper.preparemainlooper () method is called in line 7th, and this method again calls the Looper.prepare () method, as shown in the following code:

public static final void Preparemainlooper () {    prepare ();    Setmainlooper (Mylooper ());    if (process.supportsprocesses ()) {        mylooper (). mqueue.mquitallowed = false;    }}

So there is always a Looper object in the main thread of our application, so there is no need to call the Looper.prepare () method manually.

This basically makes handler's creation process fully understood, summed up is that in the main thread can directly create the handler object, and in the child threads need to call Looper.prepare () before you can create the handler object.

After reading how to create handler, let's look at how to send a message, the process is believed to be very familiar with, new to a Message object, and then can use the SetData () method or arg parameter, etc. to carry some data for the message, Then send the message out with handler, the sample code is as follows:

New Thread (New Runnable () {@Overridepublic void run () {Message message = new Message (); message.arg1 = 1; Bundle bundle = new Bundle (), bundle.putstring ("Data", "data"); Message.setdata (bundle); handler.sendmessage (message);}}). Start ();

But where did the handler send the message to? Why then can I get this message again in Handler's Handlemessage () method? It seems that we need to read the source code to relieve our doubts, Handler provides a number of ways to send messages, in addition to the Sendmessageatfrontofqueue () method, Other methods of sending messages will eventually be called into the Sendmessageattime () method, the source code of this method is as follows:

public boolean sendmessageattime (Message msg, long Uptimemillis) {    Boolean sent = false;    MessageQueue queue = Mqueue;    if (queue! = null) {        msg.target = this;        Sent = Queue.enqueuemessage (msg, uptimemillis);    }    else {        RuntimeException e = new RuntimeException (this            + "sendmessageattime () called with no Mqueue");        LOG.W ("Looper", E.getmessage (), E);    }    return sent;}

The Sendmessageattime () method receives two parameters, where the MSG parameter is the message object that we send, and the Uptimemillis parameter represents the time that the message was sent, and its value equals the number of milliseconds since the system was booted to the current time, plus the delay time. If you are not calling the Sendmessagedelayed () method, the delay time is 0, and both parameters are passed to the MessageQueue Enqueuemessage () method. What is this MessageQueue again? As you can see from the name, it is a message queue that arranges all incoming messages in the form of queues and provides a way to queue and team out. This class is created in the Looper constructor, so a looper corresponds to a MessageQueue.

Then the Enqueuemessage () method is undoubtedly the method of the queue, we look at the source of this method:

Final Boolean enqueuemessage (Message msg, long when) {if (Msg.when! = 0) {throw new Androidruntimeexception (ms    G + "This message was already in use."); } if (Msg.target = = null &&!mquitallowed) {throw new RuntimeException ("Main thread not allowed to quit    "); } synchronized (this) {if (mquiting) {runtimeexception e = new RuntimeException (Msg.target + "send            ING message to a Handler on a dead thread ");            LOG.W ("MessageQueue", E.getmessage (), E);        return false;        } else if (Msg.target = = null) {mquiting = true;        } Msg.when = when;        Message p = mmessages;            if (p = = NULL | | when = = 0 |-when < p.when) {msg.next = P;            Mmessages = msg;        This.notify ();            } else {Message prev = null;                while (P! = null && p.when <= when) {prev = p;            p = p.next; } msg.next= Prev.next;            Prev.next = msg;        This.notify (); }} return true;

First you need to know that MessageQueue does not use a collection to save all the messages, it only uses a Mmessages object to represent the currently pending message. Then observe the 16~31 line of the above code, we can see that the so-called queue is actually all the messages sorted by time, this time is of course the Uptimemillis parameter we just introduced. The specific action method calls Msg.next in the order of time, thus specifying what the next message is for each message. Of course, if you are sending the message through the Sendmessageatfrontofqueue () method, it will also call Enqueuemessage () to queue the message, but the time is 0, then the mmessages is assigned to the new queue of this message, Next the message is then specified as the Mmessages, which completes the operation to add the message to the queue header.  
Now we have seen the queue operation, where did the team operate? This requires a look at the source of the Looper.loop () method, as follows:

public static final void loop () {Looper me = Mylooper ();    MessageQueue queue = Me.mqueue; while (true) {Message msg = Queue.next ();//might block if (msg! = null) {if (Msg.target = = Nu            ll) {return; } if (me.mlogging!= null) me.mLogging.println (">>>>> dispatching to" + MSG.)            Target + "" + Msg.callback + ":" + msg.what);            Msg.target.dispatchMessage (msg);                    if (me.mlogging!= null) me.mLogging.println ("<<<<< finished to" + Msg.target + ""            + Msg.callback);        Msg.recycle (); }    }}

As you can see, this method starts at line 4th, enters a dead loop, and then constantly calls the MessageQueue next () method, which I think you have guessed, and this next () method is the queue-out method for Message Queuing. However, because the code of this method is a little bit long, I do not post it, its simple logic is if the current MessageQueue in the presence of mmessages (that is, pending messages), the message out of the team, and then let the next message become mmessages, or enter a blocking state And waited until new news was queued. Continue to the 14th line of the loop () method, and whenever a message is out of the queue, pass it to Msg.target's DispatchMessage () method, what is msg.target here? Actually is handler, you observe the above Sendmessageattime () method of the 6th line can be seen. Next, of course, look at the source of the DispatchMessage () method in handler, as follows:

public void DispatchMessage (Message msg) {    if (msg.callback! = null) {        handlecallback (msg);    } else {        if ( Mcallback! = null) {            if (Mcallback.handlemessage (msg)) {                return;            }        }        Handlemessage (msg);}    }

In line 5th, if Mcallback is not empty, call Mcallback's Handlemessage () method, otherwise call handler's Handlemessage () method directly, and pass the message object as a parameter. So I'm sure everyone will understand why the Handlemessage () method can get the message that was sent before!

Therefore, one of the most standard asynchronous message processing threads should look like this:

Class Looperthread extends Thread {public      Handler Mhandler;      public void Run () {          looper.prepare ();          Mhandler = new Handler () {public              void Handlemessage (Message msg) {                  //process incoming messages here              }          };          Looper.loop ();      }  }

Of course, this code is copied from the official Android document, but you can now look at this code, is it more profound understanding?

So we're going to go ahead and analyze why the UI can be manipulated in the same way that asynchronous message processing is used. This is because handler is always attached to the thread at the time of creation, such as our handler is created in the main thread, and in the child thread is not able to directly manipulate the UI, so we pass a series of messages sent, queued, out of the team and other links, The last call to the handler Handlemessage () method, at this point the Handlemessage () method is already running in the main thread, so of course we can do the UI operation here. The entire asynchronous message processing flow is as follows:

In addition to sending messages, there are several ways in which you can perform UI operations on child threads:

1. Handler's post () method

2. View's post () method

3. Runonuithread () Method of activity

Let's take a look at the post () method in handler, where the code looks like this:

Public Final Boolean post (Runnable R) {   return  sendmessagedelayed (Getpostmessage (R), 0);}

Originally here still called the sendmessagedelayed () method to send a message ah, and also used the Getpostmessage () method to convert the Runnable object into a message, we look at the source of this method:

Private final Message Getpostmessage (Runnable r) {    Message m = Message.obtain ();    M.callback = R;    return m;}

In this method, the value of the callback field of the message is specified as the incoming Runnable object. Hey? This callback field looks a bit familiar, oh! In Handler's DispatchMessage () method, there was a check that if the message's callback equals NULL, the Handlemessage () method is called, otherwise the Handlecallback () method is called. So let's take a look at the code in the Handlecallback () method:

Private final void Handlecallback (Message message) {    message.callback.run ();}

It's too easy! The run () method that directly invokes the Runnable object that was first passed in. So the UI operation via the handler post () method in a child thread can be written like this:

public class Mainactivity extends Activity {private Handler Handler; @Overrideprotected void OnCreate (Bundle Savedinstancestate) {super.oncreate (savedinstancestate); Setcontentview (r.layout.activity_main); handler = new Handler (); New Thread (New Runnable () {@Overridepublic void run () {Handler.post (new Runnable () {@Overridepublic void run () {//Here for UI Action});}). Start ();}}

Although the wording is much different, the principle is exactly the same, we update the UI in the Run () method of the Runnable object, and the effect is exactly the same as updating the UI in the Handlemessage () method.

Then look at the post () method in view, as shown in the code below:

public Boolean post (Runnable action) {    Handler Handler;    if (mattachinfo! = null) {        handler = Mattachinfo.mhandler;    } else {        viewroot.getrunqueue (). Post (action);        return true;    }    Return Handler.post (action);}

The original is called the handler in the post () method, I believe there is no need to explain again.

Finally, take a look at the Runonuithread () method in activity, as shown in the code below:

Public final void Runonuithread (Runnable action) {    if (thread.currentthread ()! = Muithread) {        mhandler.post ( action);    } else {        action.run ();    }}

If the current thread is not equal to the UI thread (the main thread), call handler's post () method, or call the Runnable object's run () method directly. Is there anything more clear than that?

Through all of the above source analysis, we have found that, regardless of which method to update the UI in the child thread, in fact, the principle behind is the same, must all rely on the mechanism of asynchronous message processing to achieve, and we have the process of this mechanism fully understand, is really a discriminating ears thing ah.

Reproduced "Android Handler, Message"

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.