Android進階:三、這一次,我們用最詳細的方式解析Android訊息機制的原始碼

Android丶SE開發發表於2019-04-24
決定再寫一次有關Handler的原始碼

Handler原始碼解析

一、建立Handler物件

使用handler最簡單的方式:直接new一個Handler的物件

Handler handler = new Handler();複製程式碼

所以我們來看看它的建構函式的原始碼:

 public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }複製程式碼

這段程式碼做了三件事:

1、校驗是否可能記憶體洩漏
2、初始化一個Looper mLooper
3、初始化一個MessageQueue mQueue

我們一件事一件事的看:

1、校驗是否存在記憶體洩漏

Handler的建構函式中首先判斷了FIND_POTENTIAL_LEAKS的值,為true時,會獲取該物件的執行時類,如果是匿名類,成員類,區域性類的時候判斷修飾符是否為static,不是則提示可能會造成記憶體洩漏。
問:為什麼匿名類,成員類,區域性類的修飾符不是static的時候可能會導致記憶體洩漏呢?

答:因為,匿名類,成員類,區域性類都是內部類,內部類持有外部類的引用,如果Activity銷燬了,而Hanlder的任務還沒有完成,那麼Handler就會持有activity的引用,導致activity無法回收,則導致記憶體洩漏;靜態內部類是外部類的一個靜態成員,它不持有內部類的引用,故不會造成記憶體洩漏

這裡我們可以思考為什麼非靜態類持有外部類的引用?為什麼靜態類不持有外部類的引用?

問:使用Handler如何避免記憶體洩漏呢?
答:使用靜態內部類的方式

2、初始化初始化一個Looper mLooper

這裡獲得一個mLooper,如果為空則跑出異常:

"Can't create handler inside thread that has not called Looper.prepare() "複製程式碼

如果沒有呼叫Looper.prepare()則不能再執行緒裡建立handler!我們都知道,如果我們在UI執行緒建立handler,是不需要呼叫這個方法的,但是如果在其他執行緒建立handler的時候,則需要呼叫這個方法。那這個方法到底做了什麼呢?我們去看看程式碼:

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));
    }複製程式碼

先取sThreadLocal.get()的值,結果判斷不為空,則跑出異常“一個執行緒裡只能建立一個Looper”,所以sThreadLocal裡存的是Looper;如果結果為空,則建立一個Looper。那我們再看看,myLooper()這個方法的程式碼:

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }複製程式碼

總上我們得出一個結論:當我們在UI執行緒建立Handler的時候,sThreadLocal裡已經存了一個Looper物件,所以有個疑問:
當我們在UI執行緒中建立Handler的時候sThreadLocal裡的Looper從哪裡來的?
我們知道,我們獲取主執行緒的Looper需要呼叫getMainLooper()方法,程式碼如下:

public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }複製程式碼

所以我們跟蹤一下這個變數的賦值,發現在方法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();
        }
    }複製程式碼

  • 第一步呼叫了prepare(false),這個方法我們剛才已經看了,是建立一個Looper物件,然後存到sThreadLocal中;\
  • 然後判斷sMainLooper是否為空,空則丟擲異常
  • sMainLooper不為空,則sMainLooper = myLooper()

至此sMainLooper物件賦值成功,所以,我們需要知道prepareMainLooper()這個方法在哪呼叫的,跟一下程式碼,就發現在ActivityThread的main方法中呼叫了Looper.prepareMainLooper();。現在真相大白:
當我們在UI執行緒中建立Handler的時候sThreadLocal裡的Looper是在ActivityThread的main函式中呼叫了prepareMainLooper()方法時初始化的
ActivityThread是一個在一個應用程式中負責管理Android主執行緒的執行,包括活動,廣播,和其他操作的類

3、初始化一個MessageQueue mQueue

從程式碼裡我們看出這裡直接呼叫了:mLooper.mQueue來獲取這個物件,那這個物件可能在Looper初始化的時候就產生了。我們去看看Looper的初始化程式碼:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }複製程式碼

程式碼很簡單,就是建立了MessageQueue的物件,並獲得了當前的執行緒。

至此,Handler的建立已經完成了,本質上就是獲得一個Looper物件和一個MessageQueue物件!

二、使用Handler傳送訊息

Handler的傳送訊息的方式有很多,我們跟蹤一個方法sendMessage方法一直下去,發現最後竟然呼叫了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);
    }複製程式碼

這段程式碼做了幾件事

1、給msg.target賦值,也就是Handler物件
2、給訊息設定是否是非同步訊息。
3、呼叫MessageQueue 的enqueueMessage(msg, uptimeMillis)方法
我們只關注第三步:這一步把Handler的傳送訊息轉給了MessageQueue的新增訊息的方法。
所以至此,Handler傳送訊息的任務也已經完成了,本質上就是呼叫MessageQueue自己的新增訊息的方法!

三、MessageQueue新增訊息

MessageQueue的建構函式程式碼如下:

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }複製程式碼

也沒做什麼特別的事情。我們去看看enqueueMessage(msg, uptimeMillis)方法程式碼:

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;
    }複製程式碼

程式碼很長,但是通過觀察這段程式碼我們發現這個MessageQueue實際上是個連結串列,新增訊息的過程實際上是一個單連結串列的插入過程。

所以我們知道了Handler傳送訊息的本質其實是把訊息新增到MessageQueue中,而MessageQueue其實是一個單連結串列,新增訊息的本質是單連結串列的插入

四、從訊息佇列裡取出訊息

我們已經知道訊息如何儲存的了,我們還需要知道訊息是如何取出的。
所以我們要看一下Looper.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;


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


            try {
                msg.target.dispatchMessage(msg);
            }

        }
    }複製程式碼

程式碼太長我刪了部分程式碼。可以看出這個方法主要的功能是很簡單的。

  • 獲取Looper物件,如果為空,拋異常。
  • 獲取訊息佇列MessageQueue queue
  • 遍歷迴圈從訊息佇列裡取出訊息,當訊息為空時,迴圈結束,訊息不為空時,分發出去!

但是實際上當沒有訊息的時候queue.next()方法會被阻塞,並標記mBlocked為true,並不會立刻返回null。而這個方法阻塞的原因是nativePollOnce(ptr, nextPollTimeoutMillis);方法阻塞。阻塞就是為了等待有訊息的到來。那如果在有訊息加入佇列,loop()方法是如何繼續取訊息呢?
這得看訊息加入佇列的時候有什麼操作,我們去看剛才的enqueueMessage(msg, uptimeMillis)方法,發現

if (needWake) {
    nativeWake(mPtr);
}複製程式碼

當needWake的時候會呼叫一個本地方法喚醒讀取訊息。
所以這裡看一下訊息分發出去之後做了什麼?

msg.target.dispatchMessage(msg);複製程式碼

上面講過這個target其實就是個handler。所以我們取handler裡面看一下這個方法程式碼

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

程式碼非常簡單,當callback不為空的時候呼叫callback的handleMessage(msg)方法,當callback為空的時候呼叫自己的handleMessage(msg)。一般情況下我們不會傳入callback,而是直接複寫Handler的handleMessage(msg)方法來處理我們的訊息。

喜歡我的文章的話可以點個讚的哦

寫在最後

很多人在剛接觸這個行業的時候或者是在遇到瓶頸期的時候,總會遇到一些問題,比如學了一段時間感覺沒有方向感,不知道該從那裡入手去學習,對此我整理了一些資料,需要的可以免費分享給大家,後面也會整理比較新比較火的技術分享的flutter—效能優化—移動架構—資深UI工程師 —NDK

如果喜歡我的文章,想與一群資深開發者一起交流學習的話,歡迎加入我的合作群Android Senior Engineer技術交流群:925019412

領取方式:Android技術交流群925019412


相關文章