I believe many people will have a question, why we want to read the source code, and not on the work, this problem is great, we will start from the use, and then analyze the implementation principle of these uses, so as to reflect the meaning of reading the source code.

  1. Intercepting global crashes (main thread) based on Handler and Looper to avoid APP exit.
  2. ANR monitoring based on Handler and Looper.
  3. Implement a single thread pool based on Handler.
The implementation code
class MyApplication : Application() {
    override fun onCreate(a) {
        super.onCreate()
        var startWorkTimeMillis = 0L
        Looper.getMainLooper().setMessageLogging {
            if (it.startsWith(">>>>> Dispatching to Handler")) {
                startWorkTimeMillis = System.currentTimeMillis()
            } else if (it.startsWith("<<<<< Finished to Handler")) {
                val duration = System.currentTimeMillis() - startWorkTimeMillis
                if (duration > 100) {
                    Log.e("Main thread execution takes too long"."$durationMs,$it")}}}val handler = Handler(Looper.getMainLooper())
        handler.post {
            while (true) {
                try {
                    Looper.loop()
                } catch (e: Throwable) {
                    // TODO main thread crashes
                    if(e.message ! =null&& e.message!! .startsWith("Unable to start activity")) {
                        android.os.Process.killProcess(android.os.Process.myPid())
                        break
                    }
                    e.printStackTrace()
                }
            }
        }
        Thread.setDefaultUncaughtExceptionHandler { thread, e ->
            e.printStackTrace()
            // The TODO asynchronous thread crashed and reported its own crash message}}}Copy the code

Through the above code can be implemented to intercept the UI thread crash, time-consuming performance monitoring. However, it is not possible to intercept all exceptions. If a crash occurs in the Activity’s onCreate, causing the Activity to fail to create, a black screen will appear.

Android: ANR Monitoring based on Handler and Looper

Source analysis

With the simple code above, we have implemented crash and ANR interception and monitoring, but we may not know why, including we know ANR occurs, but we need to further analyze why ANR occurs and how to resolve it. Today’s analysis questions are:

  1. How to intercept global crashes and avoid APP exits.
  2. How to implement ANR monitoring.
  3. Use Handler to implement the single thread pool function.
  4. Why should the Activity lifecycle be sent to execution with Handler?
  5. How to implement the delay operation of Handler.

Source code involved

/java/android/os/Handler.java
/java/android/os/MessageQueue.java
/java/android/os/Looper.java
/java/android.app/ActivityThread.java
Copy the code

Let’s start with the APP startup. The APP startup method is ActivityThread, which creates a Looper of the main thread in the main method, i.e. the current process creation. Looper.loop() is called at the end of the main method, which handles the main thread task scheduling. Once executed, this method means that the APP is exited. If we want to avoid the APP being exited, we must let the APP continue to execute looper.loop ().

package android.app;
public final class ActivityThread extends ClientTransactionHandler {...public static void main(String[] args) {... Looper.prepareMainLooper(); . Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code

Looper.loop()

Looper.loop() exits only when queue.next() == null. Looper.loop() exits only when queue.next() == null. In fact, queue.next() is a blocking method, and if there is no task or no active exit, it will keep blocking, waiting for the main thread to be added.

When there are tasks in the queue, the message Dispatching to… And then call MSG. Target. DispatchMessage (MSG); The system displays information Finished to… , we can analyze ANR through the printed information. Once the task is executed for more than 5 seconds, the system will trigger ANR, but we must be more strict with our APP. We can set a target for us and report statistics if the time exceeds the specified time, so as to help us optimize.

public final class Looper {
    final MessageQueue mQueue;
    public static void loop(a) {
        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;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // This must be 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);
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {}
            if(logging ! =null) {
                logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); } msg.recycleUnchecked(); }}public void quit(a) {
        mQueue.quit(false); }}Copy the code

If the main thread is abnormal, it will exit the loop, which means the APP crashes, so we need to try catch to prevent the APP from exiting. We can start another looper.loop () on the main thread to execute the main thread task. Then try-catch the looper.loop () method and it won’t exit.

Implement a single thread pool based on Handler

From looper.loop (), we can use handlers to implement a single thread pool, which, like the main thread, has the same powerful capabilities as post() immediately, postDelayed(), and postAtTime() timed.

// Incorrect usage
var handler: Handler? = null
Thread({
    handler = Handler()
}).start()
Copy the code

Can’t create handler inside thread Thread thread [thread-2,5,main] that has not called looper.prepare () This is because the Handler’s job depends on Looper, and it must create a Looper for the thread to function properly.

// Correct usage
var handler: Handler? = null
Thread({
    Looper.prepare()
    handler = Handler()
    Looper.loop()
}).start()
Copy the code

Testing:

button.setOnClickListener { handler? .post { println(Thread.currentThread()) } handler? .post { println(Thread.currentThread()) } }Copy the code

Output result:

System. The out: Thread/Thread - 2, 5, the main System. The out: Thread/Thread - 2, 5, the mainCopy the code
HandlerThread

HandlerThread is the Android encapsulation of Thread, added Handler support, implementation is to achieve the function of the previous example

val handlerThread = HandlerThread("test")
handlerThread.start()
handler = Handler(handlerThread.looper)
Copy the code

MessageQueue source code analysis

We all know that the Handler is very versatile, with immediate post(), postDelayed(), timed postAtTime(), and so on. Below is from the source code analysis is how to achieve.

public final class MessageQueue {
    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 is 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. Returnif 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 messagein 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();
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if(! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so wedo not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again fora pending message without waiting. nextPollTimeoutMillis = 0; }}}Copy the code

Messagequeue.next () is a method with blocking, which will only return when exiting or there is a task. The implementation of blocking is to use the nativePollOnce() function of the Native layer. If there is no message in the MessageQueue, nativePollOnce will not return. Always in the Native layer wait state. It does not wake up until a call to quit() exits or a call to enqueueMessage(Message MSG, long when) when a new task comes in calling the nativeWake() function of the Native layer. android_os_MessageQueue.cpp

nativePollOnce(long ptr, int timeoutMillis)

NativePollOnce is a Native function with two parameters. The first parameter is the ID of the current task queue. The second parameter is the waiting time. If it is -1, it means there is no message and enters the waiting state. If it is 0, it searches for the unwaiting message again. If it is greater than zero, it waits until the specified time and returns.

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
Copy the code

In this line of code, delayed assignment is performed to achieve postDelayed and postAtTime functions

enqueueMessage()

We may have a question, since it is a queue, first in, first out principle, so what is the output of the following code?

handler? .postDelayed({ println("Task 1")},5000) handler? .post { println(Task 2 "") } handler? .postDelayed({ println("Task 3")},3000)
// Output the resulttask2task3task1
Copy the code

This is because enqueueMessage(Message MSG, long WHEN) adds tasks that are already sorted by execution time.

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            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; }Copy the code

conclusion

Handler and Looper MessageQueue are useful for handling crashes, ANR, Handler usage, etc.