Android消息機(jī)制:Handler淺析

Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制,Handler的運(yùn)行需要底層的MessageQueue和Looper的支撐。Android規(guī)定訪問(wèn)UI只能在主線程進(jìn)行,如果在子線程中訪問(wèn)UI,程序會(huì)拋出異常,Handler的主要作用就是將一個(gè)任務(wù)切換到某個(gè)指定線程中去執(zhí)行。如在子線程獲取網(wǎng)絡(luò)數(shù)據(jù),再更新頁(yè)面UI就需要用到handler機(jī)制。
Handler主要涉及到四個(gè)類(lèi):Handler,Message,MessageQueue和Looper

MessageQueue

MessageQueue就是消息隊(duì)列,但是其內(nèi)部實(shí)現(xiàn)其實(shí)并不是隊(duì)列,而是通過(guò)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表。MessageQueue主要包含插入和讀取兩個(gè)操作,讀取操作本身又伴隨這刪除操作。插入對(duì)應(yīng)的方法是enqueueMessage,讀取對(duì)應(yīng)的方法是next。enqueueMessage就是往消息隊(duì)列中插入一條消息,next是從消息隊(duì)列中讀取一條消息并移除它。

enqueueMessage源碼如下:

boolean enqueueMessage(Message msg, long when) {
    //msg.target就是處理消息的handler
    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;
}

enqueueMessage方法返回一個(gè)布爾值,消息成功插入消息隊(duì)列返回true。方法體內(nèi)先判斷msg.target是不是為空,msg.target其實(shí)就是處理消息的handler,handler為空自然就處理不了消息。再判斷消息是不是已經(jīng)被處理了,從這個(gè)判斷可以得出消息不能被重復(fù)處理。接下來(lái)的操作就是單鏈表的插入操作,就不再贅述。如果線程是當(dāng)前是堵塞狀態(tài),最后還會(huì)喚起線程。

next方法源碼如下:

 Message next() {
    
    ......

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        //阻塞方法,主要是通過(guò)native層的epoll監(jiān)聽(tīng)文件描述符的寫(xiě)入事件來(lái)實(shí)現(xiàn)的。
       //nextPollTimeoutMillis=-1,一直阻塞不會(huì)超時(shí)。
       //nextPollTimeoutMillis=0,不會(huì)阻塞,立即返回。
       //nextPollTimeoutMillis>0,最長(zhǎng)阻塞nextPollTimeoutMillis毫秒(超時(shí)),如果期間有程序喚醒會(huì)立即返回。
        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.
                //msg.target == null表示此消息為消息屏障(通過(guò)postSyncBarrier方法發(fā)送來(lái)的)
                //如果發(fā)現(xiàn)了一個(gè)消息屏障,會(huì)循環(huán)找出第一個(gè)異步消息(如果有異步消息的話(huà)),所有同步消息都將忽略(平常發(fā)送的一般都是同步消息)                
                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.
                    //如果消息還沒(méi)有到時(shí)間,會(huì)設(shè)置一個(gè)阻塞時(shí)間
                    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.
                //沒(méi)有消息,一直阻塞
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            //消息隊(duì)列被標(biāo)記為退出狀態(tài),返回null
            if (mQuitting) {
                dispose();
                return null;
            }
               ......
        }
        ......
    }
}

next方法是一個(gè)無(wú)限循環(huán),如果消息隊(duì)列中沒(méi)有消息,next方法就會(huì)一直阻塞。有新消息時(shí),將新消息返回并移除這條消息。當(dāng)消息隊(duì)列被標(biāo)記為退出狀態(tài)時(shí),返回null,這里先賣(mài)一個(gè)關(guān)子,這個(gè)null會(huì)在文章后面講到。

Looper

Looper在Android消息機(jī)制扮演消息循環(huán)的角色,會(huì)不停的去MessageQueue中查看是否有新消息,如果有就立即處理,沒(méi)有的話(huà)就會(huì)一直堵塞在那邊。

Looper的構(gòu)造函數(shù):

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}  

構(gòu)造函數(shù)中會(huì)新建一個(gè)MessageQueue,然后將當(dāng)前的線程對(duì)象保存起來(lái)。

大家都知道創(chuàng)建Handler之前必須要現(xiàn)在線程中創(chuàng)建一個(gè)Looper對(duì)象,不然會(huì)報(bào)錯(cuò)。創(chuàng)建Looper對(duì)象也很簡(jiǎn)單,直接調(diào)用Looper.prepare()就可以了,創(chuàng)建Looper對(duì)象之后還不能實(shí)現(xiàn)消息循環(huán),必須再調(diào)用loop()方法來(lái)開(kāi)啟消息循環(huán)。讀到這里可能會(huì)有困惑,為什么在主線程創(chuàng)建Handler不需要我們自己創(chuàng)建Looper對(duì)象?答案就在ActivityThread里:

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

從源碼中可以看到在主線程啟動(dòng)時(shí)就已經(jīng)調(diào)用了Looper.prepareMainLooper()方法,該方法是Looper提供給主線程使用的,接下來(lái)又調(diào)用了 Looper.loop()開(kāi)啟消息循環(huán)。這就是為什么我們?cè)谥骶€程創(chuàng)建Handler時(shí)不需要的自己創(chuàng)建Looper對(duì)象的原因。

再回過(guò)頭來(lái)開(kāi)子線程創(chuàng)建Handler的流程:

  thread {
        Looper.prepare()
        val handler = Handler(object : Handler.Callback {
            override fun handleMessage(msg: Message): Boolean {
                doSomething()
            }
        })
        Looper.loop()
    }

首先創(chuàng)建一個(gè)Looper對(duì)象,接著創(chuàng)建Handler,然后再開(kāi)啟循環(huán)。(如上為了方便用kotlin代碼進(jìn)行舉例)
Looper提供了創(chuàng)建對(duì)象的方法之外還提供了quit和quitSafely方法,兩個(gè)方法的唯一區(qū)別就是quit會(huì)立即退出,quitSafely會(huì)等消息隊(duì)列中所有的消息處理完成之后再退出。退出之后Handler的send方法會(huì)返回false。不建議在主線程中調(diào)取quit方法,因?yàn)闀?huì)立即結(jié)束主線程。當(dāng)子線程中,應(yīng)當(dāng)在處理完所有消息之后調(diào)取quit方法來(lái)結(jié)束當(dāng)前線程。
Looper中最重要的方法就是loop方法,源碼如下:

public static void loop() {
    ...
    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);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       ...
        msg.recycleUnchecked();
    }
}

loop方法也比較好理解,他也是一個(gè)無(wú)限循環(huán)的方法。for循環(huán)中會(huì)調(diào)取MessageQueue的next方法,在之前說(shuō)過(guò)next是一個(gè)阻塞方法,所以當(dāng)消息隊(duì)列中沒(méi)有消息的時(shí)候,會(huì)一直阻塞在這里,唯一跳出循環(huán)的方式就是next方法返回了null,這里就是解答了之前在講MessageQueue的時(shí)候?yàn)槭裁匆祷豱ull。當(dāng)Looper調(diào)quit方法的時(shí)候,Looper就會(huì)調(diào)用MessageQueue的quit方法,然后MessageQueue的next方法就會(huì)返回null,這樣loop方法才能跳出無(wú)限循環(huán)。當(dāng)loop拿到了消息之后就會(huì)調(diào)用msg.target.dispatchMessage(msg)「msg.target前面講到過(guò)就是發(fā)送這條消息的Handler」將消息交給Handler來(lái)處理了。這里需要注意的是Handler的dispatchMessage是在創(chuàng)建Handler時(shí)所使用的Looper中執(zhí)行的,這就就成功的切換到指定線程中去執(zhí)行了。

Handler

Handler的作用主要是消息發(fā)送以及接收。消息發(fā)送可以通過(guò)一系列的post方法和一系列的send方法,其實(shí)post方法到最后都是通過(guò)send方法來(lái)進(jìn)行發(fā)送。

public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

查看源碼發(fā)現(xiàn)到最后都是通過(guò)MessageQueue的enqueueMessage將消息插入到消息隊(duì)列中。之后Looper就會(huì)調(diào)用MessageQueue的next方法拿到消息,然后就進(jìn)入了下一個(gè)Handler處理消息的階段,調(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);
    }
}

1.先判斷msg的callback是不是為null,這個(gè)callback對(duì)象其實(shí)就是handler發(fā)送消息時(shí)調(diào)用post方法所傳遞的Runnable參數(shù),不為空就調(diào)用handleCallback方法處理。

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

handleCallback方法也很簡(jiǎn)單,直接執(zhí)行Runnable的run方法。
2.其次檢查mCallback是否為null,部位null調(diào)用mCallback的handleMessage方法,查看源碼mCallBack其實(shí)是一個(gè)CallBack接口,CallBack具體如下:

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    boolean handleMessage(@NonNull Message msg);
}

mCallBack通過(guò)構(gòu)造函數(shù)傳入,當(dāng)你不想創(chuàng)建繼承自Handler的派生類(lèi)時(shí),就可以通過(guò)該構(gòu)造方法直接傳入一個(gè)Callback參數(shù)來(lái)實(shí)現(xiàn)消息處理。

val handler = Handler(object :Handler.Callback{
            override fun handleMessage(msg: Message): Boolean {
                ...
           
})

3.最后調(diào)用Handler的handleMessage來(lái)處理消息

處理消息的流程圖大致如下:


image.png

額外注意點(diǎn):

  1. 消息機(jī)制里需要頻繁創(chuàng)建消息對(duì)象(Message),因此消息對(duì)象需要使用享元模式來(lái)緩存,以避免重復(fù)分配 & 回收內(nèi)存。具體來(lái)說(shuō),Message 使用的是有容量限制的、無(wú)頭節(jié)點(diǎn)的單鏈表的對(duì)象池,創(chuàng)建Message的時(shí)候最好用Handler的obtainMessage來(lái)進(jìn)行創(chuàng)建,盡量避免使用new Message()來(lái)創(chuàng)建造成內(nèi)存抖動(dòng)

2.在子線程中創(chuàng)建Handler時(shí)一定要先創(chuàng)建Looper對(duì)象,然后調(diào)用loop方法開(kāi)啟循環(huán)

3.盡量使用弱持有來(lái)創(chuàng)建Handler對(duì)象

思考:

1.一個(gè)線程可以創(chuàng)建幾個(gè)Looper?

2.MessageQueue是否線程安全?如果是 怎么保證?

3.Looper在主線程中死循環(huán),為啥不會(huì)ANR?

4.Handler會(huì)造成內(nèi)存泄漏嗎?為什么?怎么解決?

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

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