Handler原理

Handler 原理

一、Handler消息發(fā)送機制

1. 發(fā)送消息

1.1 添加消息

調(diào)用Handler.sendMessageXX方法發(fā)送消息,這些方法最終都會調(diào)到MessageQueueenqueueMessage方法中,MessageQueue使用一個優(yōu)先隊列來保存添加的Message對象,執(zhí)行時間越早的Message放的越靠近隊頭。

//加入新的頭結(jié)點
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;
    //遍歷鏈表,把最快執(zhí)行的msg放到表頭
    for (;;) {
        prev = p;
        p = p.next;
        if (p == null || when < p.when) {
            break;
        }
        if (needWake && p.isAsynchronous()) {
            needWake = false;
        }
    }
    //插入新的Message節(jié)點
    msg.next = p; // invariant: p == prev.next
    prev.next = msg;
}

這樣就把一個Message加入到了MessageQueue中。下面要看的就是怎么樣取出消息。

1.2 取出消息

Looper.loop()方法中通過一個死循環(huán)來不斷的從MessageQueue中取出Message

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
//使本線程休眠
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;
    }

MessageQueue方法中也有一個for循環(huán)來從優(yōu)先隊列中找出一個符合條件的Message

  • 由于MessageQueue中保存的Message是按有限順序排列的所以第一個一定是最先執(zhí)行的,先取出第一個,然后判斷這個Message是否可以開始處理,

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

    如果不可以,計算還差多少時間可以處理這個Message,并把計算的時間賦值給nextPollTimeoutMillis,這樣當循環(huán)到下一次的時候就會調(diào)用nativePollOnce這個方法來使本線程休眠,當休眠時間到了之后就重新取出一個Message然后返回,這樣,MessageQueue.next方法就執(zhí)行結(jié)束,loop方法就能拿到一個新的Message.

  • Loop方法拿到Message之后判斷是否為空,如果是那loop方法死循環(huán)就結(jié)束,相當于Looper就停止工作。

    如果不為空就調(diào)用Message.target.dispatchMessage方法。在loop方法拿Message的過程中可能會因為MessageQueue的休眠而導致卡主

  • Message.target是一個Hander的引用,msg.target = this;HandlerenqueueMessage中會將當前Handler設置為Message.target的引用。所以這個方法最終也就是調(diào)用Handler.dispatchMessage方法,

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

    這個方法最終會調(diào)到handleMessage方法中,也就是在創(chuàng)建Handler實例時重寫的那個方法。這樣就能處理到這個Message。

[圖片上傳失敗...(image-1acb02-1593160818812)]

二、Looper與線程的數(shù)量關(guān)系

結(jié)論:每條線程只有一個Looper

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

Looper中維護了一個ThreadLoacal的靜態(tài)變量,當調(diào)用這個Looperprepare方法時會先判斷當前的線程是否有一個Looper的實例,如果沒有就創(chuàng)建一個。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal維護了一個類似于HashMap的數(shù)據(jù)結(jié)構(gòu)用線程號作為Key,這樣就保證了每條線程只會有一個對應的實例。

所以,Looper是通過ThreadLocal來保證每條線程只有一個Looper實例。如果在同一個線程中多次調(diào)用prepare方法就會拋出異常。

三、Handler內(nèi)存泄露

在實例化Handler時使用匿名內(nèi)部類來重寫handleMessage方法,這個Handler實例就持有了外部類的引用(通常會是一個Activity),在sendMessage時又會使Message引用當前Handler實例,這樣Message就會最終引用到Activity實例。由于Handler發(fā)送的消息存在延時機制導致線程可能休眠,這樣休眠線程中的Message就會一直持有Activity的引用。并且在prepareThreadLocal會持有創(chuàng)建的Looper實例的引用,而靜態(tài)的ThreadLocal又會被Looper.class持有引用。Looper.class是一個GCRoot所以在可達性分析時就不會釋放Activity。

[圖片上傳失敗...(image-170ef4-1593160818812)]

四、Handler的使用

1. 主線程上Handler的使用

Looper源碼中可以看到需用調(diào)用loop方法才能才能從MessageQueue中的取出消息并處理,但是在Activity中使用Handler時,我們并不需要去手動的prepare來調(diào)用loop方法。因為在Activity在啟動的時候已經(jīng)由Android自動prepareMainLooper。

ActivityThread.main()中已經(jīng)調(diào)用了Looper.prepareMainLooper方法,這個main方法時整個應用啟動的入口方法,所以在這個時候就會init主線程中的Looper。

2. 在子線程使用Looper

  • prepare()先調(diào)用prepare方法來在本線程創(chuàng)建一個Looper實例,
  • loop(),調(diào)用loop方法來從MessageQueue中取出數(shù)據(jù)
  • quit()/quitSafty()清空MessageQueue退出``Looper

五、多個線程往MessageQueue中發(fā)消息如何保證線程安全

使用synchronized關(guān)鍵字對enqueueMessagenext加上當前MessageQueue的鎖,這樣在多個線程中操作這個MessageQueue時就能保證每次只有一個線程能執(zhí)行。所以發(fā)送Message的delay時間由于等待鎖的原因不一定是精確的。

六、如何創(chuàng)建一個Message

通過Message.obtain()方法來獲取一個對象。Message中維護了一個Message對象的實例鏈表,當調(diào)用obtain方法時就從鏈表中取出一個Message實例,并將這個Message的標志位重置。然后返回這個Message實例。當使用完一個Message之后會調(diào)用recycleUnCheck()方法來重置這個Message,并將其加入到鏈表中。這里使用到了享元設計模式。

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    //最大保存50個
    synchronized (sPoolSync) {
        if (sPoolSize < 50) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

七 、quit和quitSafty的區(qū)別

1. quit

這個方法直接移除MessageQueue中所有的Message并將其重置。

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

2. quitSafty

這個不會移除當前已經(jīng)到達執(zhí)行時間的Message,會讓讓其正常處理完成,只會移除沒有到達處理時間的Message。

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            //所有的消息都還不執(zhí)行,直接將其移除
            removeAllMessagesLocked();
        } else {
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    //n節(jié)點及之后的節(jié)點都還不到執(zhí)行的時間
                    break;
                }
                p = n;
            }
            p.next = null;
            //將還不到執(zhí)行時間的message全部移除
            do {
                p = n;
                n = p.next;
                //把target置為null
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

在執(zhí)行著兩個方法之后會調(diào)用nativeWake(mPtr)來喚醒在休眠的next方法,這個方法如果返回一個空的messageLooper就會推出循環(huán)。

[圖片上傳失敗...(image-3daee6-1593160818812)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 首先,我將Handler相關(guān)的原理機制形象的描述為以下情景: Handler:快遞員(屬于某個快遞公司的職員) M...
    夏天之靈閱讀 260評論 0 0
  • 我的簡書:http://www.itdecent.cn/u/c91e642c4d90我的CSDN:http://...
    在代碼下成長閱讀 791評論 2 0
  • Handler原理分析: 概念: handler是Android提供給我們用來更新UI的機制,同時也是一套消息處理...
    劉筱陽閱讀 3,080評論 0 2
  • Handler簡單使用 1.使用靜態(tài)內(nèi)部類的方式繼承Handler并重寫接受的方法handleMessage。之所...
    Android小工ing閱讀 206評論 0 0
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗。 放學鈴聲...
    飄雪兒5閱讀 7,818評論 16 22

友情鏈接更多精彩內(nèi)容