Android Handler message Mechanism principle analysis

Source: Internet
Author: User

Objective

Android developers know that the UI control cannot be modified on a non-main thread because Android rules to access the UI only in the main thread, and if the UI is accessed in a child thread, the program throws an exception

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy .

Also, Android does not recommend that you take some time-consuming actions on the UI thread as a main thread, or it will cause program ANR. If we need to do some time-consuming operations and modify the UI after the end of the operation, then we need to switch to the main thread to access the UI using the Handler provided by Android. Therefore, the main reason why the system provides Handler is to solve the problem that the UI cannot be accessed in a child thread.

Overview

To understand the Handler message mechanism principle, there are several concepts to understand:

    1. UI thread

      Main thread Activitythread

    2. Message

      Handler sends and processes messages that are managed by MessageQueue.

    3. MessageQueue

      Message Queuing, which is used to store messages sent through Handler, is based on FIFO execution, and is used internally for the structure of a single-linked list.

    4. Handler

      Responsible for sending messages and processing messages.

    5. Looper

      Responsible for the message loop, loop out MessageQueue inside the message, and give the corresponding Handler to handle.

When the app starts, a UI thread is turned on, and the message loop is started, and the app keeps extracting from the message list and processing the message to the effect of running the program.
Looper is responsible for creating a MessageQueue, and then entering an infinite loop to continuously read the message from that MessageQueue, and the creator of the message is one or more Handler.
The flowchart is as follows:

The following combination of source code to specific analysis

Looper

Looper more important two methods are prepare () and loop ()

First look at the construction method

final MessageQueue mQueue;private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();}

Looper creates a new MessageQueue when it is created

Through the Prepare method can create a lopper for Handler, the source code is as follows:

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper;  // guarded by Looper.class public static void prepare() {        prepare(true); } private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            //一个线程只能有一个looper            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed)); } public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {            if (sMainLooper != null) {                throw new IllegalStateException("The main Looper has already been prepared.");            }            sMainLooper = myLooper();        }    }

You can see that the Looper object created here is saved using ThreadLocal, which briefly describes the next ThreadLocal.

ThreadLocal is a data storage class inside a thread that can store data in a specified thread, and after the data is stored, only the data stored in the specified thread can be obtained and the data cannot be obtained for other threads, thus guaranteeing that a thread corresponds to a Looper. It can be seen from the source code 一个线程也只能有一个 Looper , otherwise it throws an exception.

The Preparemainlooper () method is called by the system in Activitythread.

Activitythread.java

  public static void Main (string[] args) {Trace.tracebegin (Trace.trace_tag_activity_manager, "activitythr        Eadmain ");       Samplingprofilerintegration.start (); //...        Omit code looper.preparemainlooper ();        Activitythread thread = new Activitythread ();        Thread.attach (FALSE);        if (Smainthreadhandler = = null) {Smainthreadhandler = Thread.gethandler (); } if (false) {Looper.mylooper (). setmessagelogging (New Logprinter (Log.debug, "Activit        Ythread "));        }//End of event Activitythreadmain.        Trace.traceend (Trace.trace_tag_activity_manager);        Looper.loop ();    throw new RuntimeException ("Main thread loop unexpectedly exited"); }

This shows that when the system is created, Looper is automatically created to process the message, so we generally do not need to call Looper.prepare () manually when using Handler in the main thread. This Handler the default and Looper bindings for the main thread.
When the Looper of the Handler binding is the Looper of the main thread, the Handler can update the UI in its handlemessage, or the update UI throws an exception.
in development, we may use Handler in several places, so we can draw a conclusion: a Looper can be bound to multiple Handler , then Looper how to distinguish the Message from which Handler processing it?
continue to see source loop ()

public static void Loop () {final Looper me = Mylooper (); if (me = = null) {throw new RuntimeException ("No Looper;        Looper.prepare () wasn ' t called on this thread. ");        Final MessageQueue queue = Me.mqueue; Make sure the identity of the the the the The local process,//and keep track of what the identity toke        n actually is.        Binder.clearcallingidentity ();        Final Long ident = Binder.clearcallingidentity (); for (;;) {Message msg = Queue.next ();//might block if (msg = = NULL) {//No Message indica                TES that, the message queue is quitting.            Return }//This must is in a local variable, in case a UI event sets the logger final Printer logging = Me            . mlogging;                        if (logging! = null) {logging.println (">>>>> dispatching to" + Msg.target + "" + Msg.callback + ":" + msG.what);            } final Long Slowdispatchthresholdms = ME.MSLOWDISPATCHTHRESHOLDMS;            Final long tracetag = Me.mtracetag; if (Tracetag! = 0 && trace.istagenabled (tracetag)) {Trace.tracebegin (Tracetag, Msg.target.getTrace            Name (msg)); } final Long start = (Slowdispatchthresholdms = = 0)?            0:systemclock.uptimemillis ();            Final long end;                try {msg.target.dispatchMessage (msg); End = (Slowdispatchthresholdms = = 0)?            0:systemclock.uptimemillis ();                } finally {if (Tracetag! = 0) {trace.traceend (Tracetag);                }} if (Slowdispatchthresholdms > 0) {final long time = End-start;                            if (Time > Slowdispatchthresholdms) {slog.w (TAG, "Dispatch took" + Time + "Ms on" + Thread.CurrentThread (). GetName () + ", h=" +                            Msg.target + "cb=" + msg.callback + "msg=" + msg.what); }} if (logging! = null) {logging.println ("<<<<< finished to" + MSG.            Target + "" + msg.callback);             }//Make sure that during the course of dispatching the//identity of the thread wasn ' t corrupted.            Final Long newident = Binder.clearcallingidentity (); if (ident! = newident) {LOG.WTF (TAG, "Thread identity changed from 0x" + Long.tohex                        String (ident) + "to 0x" + long.tohexstring (newident) + "when dispatching to"            + Msg.target.getClass (). GetName () + "" + Msg.callback + "what=" + msg.what); } msg.recycleunchecked ();//Recycle Message}}

The code is much more, we pick the key to see.
The 2~6 row gets the current Looper if no exception is thrown, and a message queue is obtained MessageQueue
So if we use Handler in a child thread then we have to call Looper.prepare () and Looper.loop () manually
The system provides sample code in the code

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();            }        }

Gets to MessageQueue after the message loop is opened, constantly fetching messages from the MessageQueue, no blocking, waiting for messages. There is a call to Msg.target.dispatchMessage (msg) to process the message.

Msg.target is the Message belongs to the Handler, this will be described in detail in the following Handler will explain

So the above question can be answered, Looper does not need to consider how to distinguish the message from which Handler processing, only to open the messages loop to receive messages and processing messages. When the message is processed, msg.recycleunchecked () is called to reclaim the message.

Can you stop the message loop after you turn it on?
The answer is yes, Looper provides quit () and quitsafely () to exit.

  public void quit() {     mQueue.quit(false);  }  public void quitSafely() {     mQueue.quit(true);  }

You can see that the exit method in MessageQueue is actually called, which is described in MessageQueue.
The call to quit () exits Looper, and quitsafely () simply sets an exit token and then exits the message queue after it has been processed safely. Sending a message through Handler fails after loooper exits. If Looper is manually created in a child thread, you should exit the Looper termination message loop after the operation has finished processing.

To this Looper source analysis is over, we will summarize the work done by Looper:

    1. is created with thread binding, guaranteeing that a thread will only have one Looper instance, and that a Looper instance has only one MessageQueue
    2. Once created, the loop () is called to turn on the message loop, and it is constantly fetching messages from MessageQueue and then handing over to the Handler that the message belongs to, that is, the Msg.target property.
    3. After the message is processed, call msg.recycleunchecked to reclaim the message
Message and MessageQueue

A message is a messaging that is passed in a thread communication, and it has several key points

    1. Use what to differentiate messages
    2. Use Arg1, arg2, obj, and data to deliver
    3. The parameter target, which determines the Handler associated with the Message, is visible at the back of the Handler source.

MessageQueue

MessageQueue is responsible for managing Message Queuing, which is maintained through a single linked list of data structures.
There are three main methods in the source code:

    1. The Enqueuemessage method inserts a piece of data into the message list,
    2. Next method takes a message out of the message queue and removes it from the message queue
    3. The Quit method exits the list of messages and determines whether to exit directly by using the parameter safe

Next method

 Message Next () {//Return here if the message loop has already quit and been disposed.        This can happen if the application tries to restart a looper after quit//which are not supported.        Final long ptr = mptr;        if (ptr = = 0) {return null; } int pendingidlehandlercount =-1;        -1 only during first iteration int nextpolltimeoutmillis = 0; for (;;)            {if (Nextpolltimeoutmillis! = 0) {binder.flushpendingcommands ();            } nativepollonce (PTR, nextpolltimeoutmillis);  Synchronized (this) {//Try to retrieve the next message.                Return if found.                Final Long now = 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) {//Next message ' is  Not 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;                        if (prevmsg! = null) {Prevmsg.next = Msg.next;                        } else {mmessages = Msg.next;                        } msg.next = null;                        if (DEBUG) log.v (TAG, "returning message:" + msg);                        Msg.markinuse ();                    return msg; }} else {//No more messages.                Nextpolltimeoutmillis =-1;                }//Process the Quit message now and all pending messages has been handled.                    if (mquitting) {Dispose ();                return null; }               //... Omit code}}

You can see that the next method is an infinite loop, and if there are no messages in the message queue, then the next method will always be blocked here. When a new message arrives, the next method obtains the message and returns it to Looper to process it and remove it from the message list.

Quit method

void Quit (Boolean safe) {if (!mquitallowed) {throw new IllegalStateException ("Main thread not allowed to quit.    ");        } synchronized (this) {if (mquitting) {return;        } mquitting = true; if (safe) {removeallfuturemessageslocked ();//Remove the message that has not yet been processed} else {removeallmessageslocked ();//        Remove all messages}//We can assume mptr! = 0 because mquitting was previously false.    Nativewake (MPTR);    }}private void removeallmessageslocked () {Message p = mmessages;        while (P! = null) {Message n = p.next;        P.recycleunchecked ();    p = n; } mmessages = null;}    private void removeallfuturemessageslocked () {final Long now = Systemclock.uptimemillis ();    Message p = mmessages; if (P! = null) {if (P.when > Now) {removeallmessageslocked ();//Remove messages not yet processed} else {//processing            Messages do not handle message n; for (;;)         {n = p.next;       if (n = = null) {return;                } if (N.when > Now) {break;            } p = N;            } p.next = null;                do {p = n;                n = p.next;            P.recycleunchecked ();        } while (n! = null); }    }}

As can be seen from the code above, when safe is true, only all messages that have not been triggered are removed, and all messages are removed when safe is false, when the message being processed is not processed.

Handler

Handler is the class that we use most, primarily for sending messages and processing messages.

First look at the construction method

Public Handler (Callback Callback, Boolean async) {if (Find_potential_leaks) {final class<? extends            Handler> Klass = GetClass (); if (Klass.isanonymousclass () | | klass.ismemberclass () | | klass.islocalclass ()) && (Klass.getmo Difiers () & modifier.static) = = 0) {LOG.W (TAG, "the following Handler class should be STATIC or leaks            Might occur: "+ klass.getcanonicalname ());        }}//Gets the Looper instance of the current thread and throws an exception if it does not exist Mlooper = Looper.mylooper (); if (Mlooper = = null) {throw new RuntimeException ("Can ' t create handler inside thread that has        Not called Looper.prepare () ");        }//MessageQueue message Queue Mqueue = Mlooper.mqueue in the associated Looper;        Mcallback = callback; masynchronous = async;}        Public Handler (Looper Looper, Callback Callback, Boolean async) {mlooper = Looper;        Mqueue = Looper.mqueue; McaLlback = callback; masynchronous = async;}

There are three main parameters of the construction method

    1. Looper does not pass a value call Looper.mylooper () gets the Looper instance of the current thread, and the value is used, typically when the Handler is used in a child thread.
    2. Callback Handler callback when processing a message
    3. Whether async is an asynchronous message. This is related to the Barrier concept in Android, which adds a Barrier (monitor) to the Looper when the View is drawn and laid out, so that messages that are synchronized in subsequent message queues will not be executed, so that the UI drawing is not affected, but only asynchronous messages can be executed. The so-called asynchronous message is only reflected in this, when Async is true, the message can continue to be executed and not be postponed.

As you can see from the source code, because the UI thread will automatically create the Looper instance when it starts, it is generally not necessary to pass the Looper object when we use Handler in the UI thread. In the child thread, you must manually call the Looper.prepare and Looper.loop methods, and pass it to Handler, otherwise you will not be able to use, this is certainly a lot of children shoes have encountered.
After getting the Looper object, Handler gets the MessageQueue message queue in Looper, which is associated with the MessageQueue.

Related to MessageQueue, then let's look at how Handler sends the message.

Handler sends the message method a lot, actually finally all is called the Enqueuemessage method, looks at the picture to speak

Main View Enqueuemessage method

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;        if (queue == null) {            RuntimeException e = new RuntimeException(                    this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);            return false;        }        return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this;    if (mAsynchronous) {        msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);}

You can see that when you send a message, target = This is the current Handler object, and the MessageQueue Enqueuemessage method is called, so the message is queued and then processed by Looper.

Children's shoes should remember that before talking about Looper, when Looper turns on the message loop, it will constantly take out messages from MessageQueue and call Msg.target.dispatchMessage (msg) to process the message.

Next, let's see how Handler receives the message, the DispatchMessage method.

    public interface Callback {        public boolean handleMessage(Message msg);    }    /**     * Subclasses must implement this to receive messages.     */    public void handleMessage(Message msg) {    }    /**     * Handle system messages here.     */    public void dispatchMessage(Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }

Can see Handler receive the message, just call an empty method handlemessage is not a bit familiar, look at the Handler code we wrote many times

private Handler mHandler = new Handler() {        public void handleMessage(Message msg) {            switch (msg.what) {                case value:                    break;                default:                    break;            }        }    };

Yes, that's exactly what we did when we created our own Handler, we handled the message and then processed the message based on the Msg.what identity.

Summarize

When the app starts, the UI thread, which is the main thread activitythread, is invoked in the main method of Activitythread, Looper.preparemainlooper () and Looper.loop () Start Looper 。
A MessageQueue instance is created at startup, and there is only one instance, and then the message is constantly fetched from the MessageQueue, no blocking wait messages, and the Looper calls Msg.target.dispatchMessage (msg) processing the message.
We need to create the Handler instance when we use Handler, Handler will get the Looper instance associated with the current thread when it is created, and the message queue Looper in MessageQueue. Then, when the message is sent, it automatically sets the target to Handler itself and puts the message in MessageQueue, which is handled by Looper. Handler is overridden when it is created.
The message is processed in the Handlemessage method.
If you want to use Handler in a child thread, you need to create a new Looper instance and pass it to Handler.

And look at the flowchart.

At last

Because the length is longer, the Handler of the children's shoes certainly uses the consummate perfection, therefore finally did not write the example.
I write a blog soon, write bad place, hope children shoes Haihan.

Android Handler message Mechanism principle analysis

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.