Daily Question: Android messaging mechanism, I need to say it again!

Keywords: Android network less

Handler is inevitably used in our daily development. Although Handler mechanism is not the same as Android's message mechanism, Handler's message mechanism has already been familiar with Android's development and is very important.

Through this article, you can easily get the answer to the following questions:

1. What are the principles of Handler, Looper, Message and Message Queue and their relationships?
2. What is the MessageQueue storage structure?
3. Why do sub-threads have to call Looper.prepare() and Looper.loop()?

Simple Use of Handler

I believe nobody can't use Handler, right? Assuming that you are dealing with a time-consuming task in Activity, you need to update the UI to see how we normally handle it.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main3)
    // Request network
    subThread.start()
}

override fun onDestroy() {
    subThread.interrupt()
    super.onDestroy()
}

private val handler by lazy(LazyThreadSafetyMode.NONE) { MyHandler() }
private val subThread by lazy(LazyThreadSafetyMode.NONE) { SubThread(handler) }

private class MyHandler : Handler() {
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        // Main Thread Processing Logic, which generally requires weak references to hold Activity instances to avoid memory leaks
    }
}

private class SubThread(val handler: Handler) : Thread() {
    override fun run() {
        super.run()
        // Time-consuming operations such as making network requests

        // When the network request is finished, we have to inform the UI to refresh in a loud voice. We will consider Handler directly, but other solutions will not be considered for the time being.
        // The first method is that, in general, this data is the content of the request result parsing.
        handler.obtainMessage(1,data).sendToTarget()
        // The second method
        val message = Message.obtain() // Use Message. get () to initialize as much as possible
        message.what = 1
        message.obj = data // Usually this data is the content of request result parsing
        handler.sendMessage(message)
        // The third method
        handler.post(object : Thread() {
            override fun run() {
                super.run()
                // Processing update operations
            }
        })
    }
}

The above code is very simple, because the network request is a time-consuming task, so we opened a new thread, and after the end of the network request parsing, we notified the main thread to update the UI through Handler. The simple use of three ways, careful small partners may find that the first and second methods are actually one. Sample. It is worth mentioning that we should use Message. get () instead of new Message() to initialize the message as much as possible. Mainly, Message. get () can reduce the application of memory.

By your suggestions in the previous article, we will try to paste as few source codes as possible. You can easily find that all the methods mentioned above will eventually call this 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);
}

The code above presents a MessageQueue and finally calls the MessageQueue#enqueueMessage method for message queuing. We have to briefly talk about the basic situation of MessageQueue.

MessageQueue

As the name implies, MessageQueue is a message queue, that is, a container for storing multiple message messages. It uses a one-way linked list data structure instead of a queue. Its next() points to the next Message element in the list.

boolean enqueueMessage(Message msg, long when) {
    // ... omit some checking code
    synchronized (this) {
        // ... omit some checking code
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

From the implementation of enqueueMessage(), the main operation of enqueueMessage() is actually the insertion of a single linked list. There are not many explanations here. We should probably pay more attention to its next():

Message next() {
    // ...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // ...
        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 is 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;
            }
            //...
        }
        //...
        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

The next() method is actually very long, but we've only posted a very small part of it. As you can see, there's only an infinite loop for (; (vii) calling a nativePollOnce(long, int) method inside the loop. This is a Native method that actually blocks the current call stack thread nextPollTimeoutMillis milliseconds through MessageQueue in the Native layer.

Following are the blocking manifestations of different nextPoll Timeout Millis values:

1. less than 0, blocking until awakened;
2. Equal to 0, not blocking;
3. Over 0, the longest blocking time is next Poll Timeout Millis milliseconds, during which it will return immediately if awakened.
As you can see, the initial value of nextPoll Timeout Millis is 0, so it will not block. It will fetch the Message object directly. If it does not fetch the data of the Message object, it will set nextPoll Timeout Millis to - 1. When the condition of less than 0 is satisfied, it will be blocked until other places call another one. The external Native method, nativeWake(long), wakes up. If the value is fetched, the message object is returned directly.

Originally, the nativeWake(long) method had a call in the previous MessageQueue#enqueueMessage method, which was called when the MessageQueue entered the queue message.

Now you know: Handler sends a Message, which is stored in MessageQueue, queued by MessageQueue enqueueMessage method, and rotated by MessageQueue next method. This inevitably raises the question of who called the MessageQueue next method? Yes, it's Looper.

Looper

Looper acts as a message loop in Android's message mechanism. Specifically, it keeps checking whether there are new messages from MessageQueue through next(), and if there are new messages, it will process them immediately, otherwise it will be blocked by MessageQueue.

Let's look directly at Looper's most important method: 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.");
    }
    // ...

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        //...
        try {
            // Distribute messages to handler for processing
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            // ...
        }
        // ...
    }
}

The method eliminates a lot of code and retains only the core logic. As you can see, the Looper object is first obtained through the myLooper() method, and if the Looper returns empty, an exception is thrown directly. Otherwise, enter a for (; (vii) loop, call the MessageQueue#next() method for rotation training to get the Message object, and exit the loop() method directly if the obtained Message object is empty. Otherwise, you get the Handler object directly through msg.target and call the Handler dispatchMessage () method.

Let's first look at the Handler dispatchMessage () method implementation:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

The code is relatively simple. If Message sets a callback, call message.callback.run() directly, otherwise determine whether `m'has been initialized.

Let's look again at the myLooper() method:

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

See what sThreadLocal is:

static final ThreadLocal sThreadLocal = new ThreadLocal();
What is this ThreadLocal?

ThreadLocal

Regarding ThreadLocal, we directly adopt the content of Yan Zhenjie's article.

The first impression of seeing ThreadLocal is that it's related to threads. That's true, but be aware that it's not a thread, otherwise it's called LocalThread.

ThreadLocal is used to store the data of a specified thread. When the scope of some data is the specified thread and the data needs to run through all the execution processes of the thread, ThreadnLocal can be used to store data. When a thread uses ThreadnLocal to store data, only the thread can read the stored data. Threads other than this thread cannot read the data.

After reading the above paragraph, some readers still don't understand the role of ThreadLocal. Let's raise a chestnut.

ThreadLocal<Boolean> local = new ThreadLocal<>();
// Set the initial value to true.
local.set(true);

Boolean bool = local.get();
Logger.i("MainThread The read value is:" + bool);

new Thread() {
    @Override
    public void run() {
        Boolean bool = local.get();
        Logger.i("SubThread The read value is:" + bool);

        // Set the value to false.
        local.set(false);
    }
}.start():

// The main thread sleeps for 1 second to ensure that the upper sub-thread is finished before executing the following code.
Thread.sleep(1000);

Boolean newBool = local.get();
Logger.i("MainThread The new values read are:" + newBool);

There's nothing to say about the code. Printed logs, you'll see this:

The value read by MainThread is true:
The value read by SubThread is: null
The value read by MainThread is true:

The first Log is unquestionable, because the value is set to true, because there is nothing to say about the printed results. For the second Log, according to the above description, data stored by a thread using ThreadLocal can only be read by that thread, so the result of the second Log is: null. The ThreadLocal value is set to false in the sub-thread, and then the third Log will be printed. In principle, the ThreadLocal value set in the sub-thread does not affect the data of the main thread, so the printing is true.

The experimental results show that even if the ThreadLocal object is the same, the operations of set() and get() methods of any thread are independent and independent of each other.

Looper.myLooper()

Let's go back to Looper.myLooper():

static final ThreadLocal sThreadLocal = new ThreadLocal();
Let's see where we operate on sThreadLocal.

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

So you know, that's why Looper.prepare() has to be called before using Handler in a subthread.
You may wonder that I didn't ask Looper.prepare() when I used the main thread.

Originally, in ActivityThread, we had to display the call to Looper.prepareMainLooper():

 public static void main(String[] args) {
        // ...
        Looper.prepareMainLooper();
        // ...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //...
        Looper.loop();
        // ...
    }
//Let's look at Looper..prepareMainLooper(): 

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

Android Development Information + Interview Architecture Information Free to Share and Click on Links to Receive

"Android Architects Necessary Learning Resources Free Receipt (Architecture Video + Interview Thematic Documents + Learning Notes)"

Posted by amsgwp on Thu, 25 Jul 2019 00:19:28 -0700