Android源碼剖析:基於 Handler、Looper 實現攔截全局崩潰、監控ANR等

相信很多人都會有一個疑問,我們爲何要去閱讀源碼,工作上又用不上,這個問題很棒,我們就先從使用出發,然後分析這些用法的實現原理,這樣才能體現出閱讀源碼的意義。

  1. 基於 Handler 和 Looper 攔截全局崩潰(主線程),避免 APP 退出。
  2. 基於 Handler 和 Looper 實現 ANR 監控。
  3. 基於 Handler 實現單線程的線程池。

實現代碼

class MyApplication : Application() {
    override fun onCreate() {
        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("主線程執行耗時過長","$duration 毫秒,$it")
                }
            }
        }
        val handler = Handler(Looper.getMainLooper())
        handler.post {
            while (true) {
                try {
                    Looper.loop()
                } catch (e: Throwable) {
                    // TODO 主線程崩潰,自行上報崩潰信息
                    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()
            // TODO 異步線程崩潰,自行上報崩潰信息
        }
    }
}

通過上面的代碼就可以就可以實現攔截UI線程的崩潰,耗時性能監控。但是也並不能夠攔截所有的異常,如果在Activity的onCreate出現崩潰,導致Activity創建失敗,那麼就會顯示黑屏。

ANR獲取堆棧信息《Android:基於 Handler、Looper 實現 ANR 監控,獲取堆棧

源碼剖析

通過上面簡單的代碼,我們就實現崩潰和ANR的攔截和監控,但是我們可能並不知道是爲何實現的,包括我們知道出現了ANR,但是我們還需要進一步分析爲何處出現ANR,如何解決。今天分析的問題有:

  1. 如何攔截全局崩潰,避免APP退出。
  2. 如何實現 ANR 監控。
  3. 利用 Handler 實現單線程池功能。
  4. Activity 的生命週期爲什麼用 Handler 發送執行。
  5. Handler 的延遲操作如何實現。

涉及的源碼

/java/android/os/Handler.java
/java/android/os/MessageQueue.java
/java/android/os/Looper.java
/java/android.app/ActivityThread.java

我們先從APP啓動開始分析,APP的啓動方法是在ActivityThread中,在main方法中創建了主線程的Looper,也就是當前進程創建。並且在main方法的最後調用了 Looper.loop(),在這個方法中處理主線程的任務調度,一旦執行完這個方法就意味着APP被退出了,如果我們要避免APP被退出,就必須讓APP持續執行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");
    }
}

Looper.loop()

那我們進一步分析Looper.loop()方法,在這個方法中寫了一個循環,只有當 queue.next() == null 的時候才退出,看到這裏我們心裏可能會有一個疑問,如果沒有主線程任務,是不是Looper.loop()方法就退出了呢?實際上queue.next()其實就是一個阻塞的方法,如果沒有任務或沒有主動退出,會一直在阻塞,一直等待主線程任務添加進來。

當隊列有任務,就會打印信息 Dispatching to ...,然後就調用 msg.target.dispatchMessage(msg);執行任務,執行完畢就會打印信息 Finished to ...,我們就可以通過打印的信息來分析 ANR,一旦執行任務超過5秒就會觸發系統提示ANR,但是我們對自己的APP肯定要更加嚴格,我們可以給我們設定一個目標,超過指定的時長就上報統計,幫助我們進行優化。

public final class Looper {
    final MessageQueue mQueue;
    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;
        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() {
        mQueue.quit(false);
    }
}

如果主線程發生了異常,就會退出循環,意味着APP崩潰,所以我們我們需要進行try-catch,避免APP退出,我們可以在主線程再啓動一個 Looper.loop() 去執行主線程任務,然後try-catch這個Looper.loop()方法,就不會退出。

基於 Handler 實現單線程的線程池

從上面的 Looper.loop() ,我們可以利用 Handler 實現單線程池功能,而且這個線程池和主線程一樣擁有立刻執行post()、延遲執行postDelayed()、定時執行postAtTime()等強大功能。

// 錯誤用法
var handler: Handler? = null
Thread({
    handler = Handler()
}).start()

當我們在異步線程執行上面的代碼,就會報錯 Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()。 這個是因爲 Handler 的工作是依靠 Looper ,必須爲線程創建 Looper 才能正常功能,正確的用法如下:

// 正確用法
var handler: Handler? = null
Thread({
    Looper.prepare()
    handler = Handler()
    Looper.loop()
}).start()

測試:

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

輸出結果:

System.out: Thread[Thread-2,5,main]
System.out: Thread[Thread-2,5,main]
HandlerThread

HandlerThread 是 Android 對Thread的封裝,增加了Handler的支持,實現就是實現了前面例子的功能

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

MessageQueue 源碼剖析

我們都知道Handler的功能非常豐富,擁有立刻執行post()、延遲執行postDelayed()、定時執行postAtTime()等執行方式。下面就從源碼分析是如何實現的。

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.  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;
                }

                // 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 we do not run them again.
            pendingIdleHandlerCount = 0;

            // 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;
        }
    }
}

MessageQueue.next() 是一個帶有阻塞的方法,只有退出或者有任務纔會return,起阻塞的實現是使用Native層的 nativePollOnce() 函數,如果消息隊列中沒有消息存在nativePollOnce就不會返回,一直處於Native層等待狀態。直到調用 quit() 退出或者調用 enqueueMessage(Message msg, long when) 有新的任務進來調用了Native層的nativeWake()函數,纔會重新喚醒。 android_os_MessageQueue.cpp

nativePollOnce(long ptr, int timeoutMillis)

nativePollOnce 是一個帶有兩個參數的Native函數,第一個參數是作爲當前任務隊列ID;第二個參數是等待時長,如果是-1,就代表無消息,會進入等待狀態,如果是 0,再次查找未等待的消息。如果大於0,就等到指定時長然後返回。

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

在這行代碼進行延時的賦值,從而實現postDelayed、postAtTime的功能

enqueueMessage()

看到這裏我們可能會有一個疑問,既然是隊列,先進先出的原則,那麼以下代碼輸出的結果是如何?

handler?.postDelayed({ println("任務1") },5000)
handler?.post { println("任務2") }
handler?.postDelayed({ println("任務3") },3000)
// 輸出結果
任務2
任務3
任務1

之所以是如此,是因爲在 enqueueMessage(Message msg, long when) 添加任務的時候已經就已經按照執行的時間要求做好了排序。

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

總結

經過上述的分析,我覺得弄懂Handler和Looper MessageQueue還是很有意義的,可以幫助我們更好處理崩潰、ANR、Handler的使用等。

好了,文章到這裏就結束瞭如果你覺得文章還算有用的話,不妨把它們推薦給你的朋友。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章