Android message mechanism

Hits: 0

[]what is [handler]

A set of message processing mechanism defined in Android, in order to realize the communication between threads, encapsulates a set of message creation, transmission, processing mechanism, non-blocking message transmission mechanism , can send the information of sub-threads to the main thread for use, such as updating the UI

Handler

Message

MessageQueue message queue, singly linked list to store messages

Looper bollard

Handler basic usage

//@Deprecated Handler no parameter constructor default mLooper = Looper.myLooper(); This is not clear enough
 Handler  handler = new  Handler ()
//pass in Looper
Handler handler= new Handler(Looper.myLooper()) 
//pass in Looper and callback
Handler handler= new Handler(Looper.myLooper(), callback)
//
Handler uiHandler = new Handler(Looper.getMainLooper());

static class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
         //
        }
}
//Instantiate 
private   MyHandler mMyHandler = new MyHandler();

Create Message

Message msg = new Message();
//The incoming handler and the bottom one have a consistent cache mechanism to avoid repeated creation of instance objects
Message msg = Message.obtain(handler);
Message msg = handler.obtainMessage();

[sendMessage] _

handler.sendMessage(msg);
handler.sendMessageDelayed(obtain,time);

handler.post method to send

//handler.post 
public  final  boolean  post (@NonNull Runnable r)  {
     //sendMessageDelayed is the same as the principle in sendMessage mentioned above 
       return   sendMessageDelayed(getPostMessage(r), 0 );
}
//handler.postDelayed
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//Runnable is encapsulated as callback of Message 
private  static Message getPostMessage (Runnable r)  {
        Message m = Message.obtain();
        m.callback = r;
        return m;
}

handler.post is also called in activity.runOnUiThread

public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
}

Fundamentals of Handlers

//The main() method of ActivityThread calls Looper.prepareMainLooper() 
public  static  void  main (String[] args) {

//The UI thread has called the following method by default to create a Looper object, which is automatically called by the system
Looper.prepareMainLooper();
……
//Call loop() to enter an infinite loop, polling to get messages
Looper.loop();
//Normally will not go to this 
throw  new RuntimeException( "Main thread loop unexpectedly exited" );
}

First look at the prepareMainLooper method of Looper

prepare(false) is called directly in prepareMainLooper;

public static void prepare() {
   //The no-parameter method passes in true, and exits are allowed by default 
  prepare( true );
}
// prepare(false), so the false passed in by the main thread is 
private  static  void  prepare ( boolean quitAllowed ) {
     if (sThreadLocal. get () != null ) {
         // Knowledge point: each thread can only Create a Looper 
        throw  new RuntimeException( "Only one Looper may be created per thread" );
    }
    //Instantiate a Looper and store it in ThreadLocal 
    sThreadLocal.set ( new Looper(quitAllowed)) ;
}

–> new Looper(quitAllowed)

//The corresponding MessageQueue object is initialized when the Looper constructor 
//Knowledge point: a Looper corresponds to a MessageQueue 
private  Looper ( boolean quitAllowed)  {
     //The incoming quitAllowed is 
        mQueue = new used in the MessageQueue.quit(boolean safe) method MessageQueue(quitAllowed);
     //Looper is bound to the current thread
        mThread = Thread.currentThread();
}

How does Looper connect with Handler and Message, let’s see how the handler sends Message

handler.sendMessage(Message msg)

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0 ;
        }
      //SystemClock.uptimeMillis() The number of milliseconds from boot to now (the time the phone sleeps is not included) 
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

public boolean sendMessageAtTime (@NonNull Message msg, long uptimeMillis)  {
     //The assignment of mQueue comes from mLooper.mQueue 
        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);
}

handler.enqueueMessage calls the queue.enqueueMessage(msg, uptimeMillis) method

boolean enqueueMessage(Message msg, long when) {
        ……
       //Multiple threads send messages to MessageQueue, lock to ensure thread safety, 
        synchronized ( this ) {
        ……
        }
        return true;
    }

In the Message class, there is also a variable of type Message named next, which is used to store the next Message to be processed. MessageQueue forms a singly linked list of multiple Messages through for (;😉 and a series of judgments in the order of uptimeMillis.

Next look at Looper’s loop method

There is also a for (;😉 looping to get Message msg = queue.next();

//myLooper() calls sThreadLocal.get();
final Looper me = myLooper();
    ……
for (;;){
     //next() is also locked internally through synchronized to ensure thread safety when fetching 
    Message msg = queue .next();
    ……
// target is Handler
msg.target.dispatchMessage(msg);
 ……
//
msg.recycleUnchecked();
}

Handler.dispatchMessage(Message msg)

public  void  dispatchMessage ( @NonNull Message msg ) {
  //The callback of Message mentioned above, that is, Runnable, if it is not empty, it means that it is sent by post 
        if (msg.callback != null ) {
         //handleCallback is called between message.callback.run() , which is Runnable.run()
            handleCallback(msg);
        } else {
         //Handler's mCallback is passed in the assignment through the Handler's constructor 
            if (mCallback != null ) {
                 //mCallback's handleMessage 
                if (mCallback.handleMessage(msg)) {
 //If it returns true, it will return, Will not continue to go to the handleMessage method of the Handler below 
                    return ;
                }
            }
            handleMessage(msg);
        }
    }

Why does the creation of the Handler in the child thread report an error, and why does the creation in the main thread not report an error?

public Handler(@Nullable Callback callback, boolean async) {
        ……
        //myLooper() calls sThreadLocal.get();
        mLooper = Looper.myLooper();
        if (mLooper == null ) {
             // If mLooper is empty, an error will be reported, indicating that the Looper.prepare() method needs to be executed first 
            throw  new RuntimeException(
                 "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Combined with the above, Looper.prepareMainLooper() has been called by default in the main thread, so it will not report an error, and the child thread will report an error without it.

How to use Handler in child thread

Combined with the above, the use of Handler in the child thread needs to perform two operations: Looper.prepare() and Looper.loop(), because when the above mLooper is empty, that is, sThreadLocal.get() is empty, and Looper.prepare() It is sThreadLocal.set(new Looper(quitAllowed)), and also call Looper.loop() to open the message loop and wait for receiving messages , so don’t forget to terminate the Looper when you don’t need it, and callLooper.myLooper().quit()

Is the child thread necessarily unable to update the UI? It must not be possible to do network operations in the main thread?

It is also possible to update the UI in the child thread, but the UI created by the child thread can be updated. If you directly update the UI created by the main thread, an error will be reported.

//ViewRootImpl.checkThread() 
void  checkThread ()  {
 //Because the current thread is detected 
    if (mThread != Thread.currentThread()) {
         throw  new CalledFromWrongThreadException(
                 "Only the original thread that created a view hierarchy can touch its views." );
    }
}

There is also no error when creating a child thread in onCreate to update the UI directly

The old version of Android will not report errors when performing network operations in the main thread. The new understanding adds verification and reports errors, so life can remove the verification and perform network operations in the main thread.

How the main thread sends messages to child threads through Handler

// The main thread delays sending a message to the child thread

// via HandlerThread

HandlerThread

IntentService

The difference between View.post and Handler.post

I mentioned Handler.post above and then look at the principle of View.post

View.post

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        //mRunQueue = new HandlerActionQueue();
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

      public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().postDelayed(action, delayMillis);
        return true;
    }

View.post finally executes the message through Handler.post. The execution process is as follows:

  1. If View.post is called before performTraversals, the message will be saved, and then called through the Handler in ViewRootImpl when dispatchAttachedToWindow.
  2. If View.post is called after performTraversals, it is called directly through the Handler in ViewRootImpl.

At the same time, this is why View.post can get the width and height information of View?

Because when the Runnable of View.post is executed, the performTraversals have been executed, that is, the measure layout draw method of the View has been executed, and the width and height information of the View can naturally be obtained.

What is IdleHandler

Looper loops infinitely in the main thread, why not ANR, if it responds to user events

Looper is blocked – the main thread is also blocked – the ActivityThread main function is also blocked, and it will not throw an exception at the last step

When the application starts, there can be not only one ActivityThread main thread, but also two other Binder threads : ApplicationThread and ActivityManagerProxy , which are used to communicate with the system process and receive notifications sent by the system process.

How to avoid memory leak caused by handler

Static inner class , Handler can’t call non-static methods in Activity, so add” weak reference

Call handler.removeCallbacksAndMessages(null) in time when there is a delay in the destruction of the message

What is ThreadLocal

What is the principle of the epoll mechanism when the Looper is blocked?

Handler’s synchronization barrier mechanism

glide

You may also like...

Leave a Reply

Your email address will not be published.