安卓Handler機(jī)制學(xué)習(xí)筆記附面試問(wèn)題

基礎(chǔ)知識(shí)

  • Handler 機(jī)制相關(guān)的概念包括 Handler、Message 、Message Queue、Looper具體如下圖


    相關(guān)的概念

    核心方法

接下來(lái)我們將四個(gè)相關(guān)的類(lèi)一個(gè)個(gè)按著源碼來(lái)看一遍

Message

  • Message是信息傳遞的載體里面沒(méi)有太多邏輯就是包括一些信息傳遞機(jī)制里面必須要用到的屬性主要看其構(gòu)造函數(shù)的源碼
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    /*package*/ Handler target;
    /*package*/ Runnable callback;

    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;
        return m;
    }
  • 可以看出來(lái)是有一個(gè)Message池的,可以避免過(guò)多創(chuàng)建對(duì)象,造成系統(tǒng)浪費(fèi)
  • 其中最重要的兩個(gè)屬性是target和callback分別是Handler和Runnable類(lèi)型的其作用我們后面會(huì)講到的

Handler

它在消息傳遞過(guò)程中扮演著重要的角色,是消息的主要處理者,說(shuō)白了,就是發(fā)消息,收消息,最終處理消息其主要屬性包括

  • 當(dāng)前Handler實(shí)例所在線程的Looper對(duì)象:mLooper = Looper.myLooper()

  • Looper的消息隊(duì)列賦值給mQueue:mQueue = mLooper.mQueue

  • Callback 來(lái)處理消息回調(diào):mCallback = callback

注意點(diǎn)

  • Handler有且只能綁定一個(gè)線程的Looper
  • Handler的消息是發(fā)送給Looper的消息隊(duì)列MessageQueue,需要等待處理
  • 調(diào)用Handler構(gòu)造函數(shù)前要先構(gòu)造Looperhe 和Message Queue,在主線程中可以直接調(diào)用是因?yàn)閯?chuàng)建主線程的時(shí)候會(huì)自動(dòng)調(diào)用Looper.prepareMainLooper和Looper.prepare,而其他線程是沒(méi)有這一步的需要前創(chuàng)建Looper和Message Queue

以上是handler創(chuàng)建部分

sendMessage()

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;  //獲得當(dāng)前的消息隊(duì)列
        if (queue == null) {   //若是在創(chuàng)建Handler時(shí)沒(méi)有指定Looper,就不會(huì)有對(duì)應(yīng)的消息隊(duì)列queue ,自然就會(huì)為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(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;   //這個(gè)target就是前面我們說(shuō)到過(guò)的
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最終調(diào)用sendMessageAtTime()這個(gè)方法,此方法注意點(diǎn)有兩個(gè)

  • msg.target = this 將Message的目標(biāo)handler設(shè)置為本身
  • queue.enqueueMessage(msg, uptimeMillis)將這個(gè)message加入到當(dāng)前的目標(biāo)消息隊(duì)列

以上是handler發(fā)送消息的部分

dispatchMessage()

前面說(shuō)了消息的發(fā)送,交給Looper等待處理,處理完后會(huì)重新通知Handler處理,那么,是怎樣通知Handler處理消息的呢?秘密就在dispatchMessage()這個(gè)方法中

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

    /**
     * 子類(lèi)必須實(shí)現(xiàn)這個(gè)來(lái)接收消息
     */
    public void handleMessage(Message msg) {
    }

當(dāng)Looper處理完Message后,會(huì)使用到Message的target,即上面說(shuō)到的target,即發(fā)送消息的那個(gè)Handler,Looper會(huì)調(diào)用Handler的dispatchMessage()方法分發(fā)消息,所以前面在enqueueMessage()發(fā)送消息的時(shí)候,為什么非得指明Message的target就是這個(gè)道理。

回到dispatchMessage()這個(gè)方法:
1、首先會(huì)判斷Message的callback是否為空,此處的callback就是前面我們?cè)贛essage中說(shuō)到的,在靜態(tài)方法創(chuàng)建Message時(shí),可以指定的callback,若不為空,則將結(jié)果回調(diào)到callback中;
2、若Handler的mCallback 不為空,也一樣的道理。
3、平時(shí)我們都沒(méi)有傳入這個(gè)callback,而是直接實(shí)現(xiàn)handleMessage()這個(gè)方法,在這個(gè)方法中處理更新UI任務(wù)。

以上就是Handler發(fā)送和接收消息的基本過(guò)程:把消息發(fā)送到隊(duì)列—>然后喝茶等待—>接收消息—>分發(fā)消息—>在回調(diào)中處理。

MessageQueue

前面我們知道,Handler發(fā)送消息會(huì)調(diào)用MessageQueue的enqueueMessage()方法

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

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

以上就是消息隊(duì)列插入消息的過(guò)程原理,通過(guò)單向鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)消息。

Looper

Looper在Handler機(jī)制中扮演著關(guān)鍵的一環(huán),他是循環(huán)處理消息的發(fā)動(dòng)機(jī)),它不斷的從消息隊(duì)列中取出的消息,處理,然后分發(fā)處理事件。每個(gè)線程都可以且只能綁定一個(gè)Looper。主線程之所以能處理消息,也是因?yàn)樵贏PP啟動(dòng)時(shí),在ActivityThread中的main()方法中就已經(jīng)啟動(dòng)了Looper循環(huán)。

loop( )的源碼

public static void loop() {
        final Looper me = myLooper();   //獲得當(dāng)前的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        final MessageQueue queue = me.mQueue;  //獲取當(dāng)前Looper的消息隊(duì)列
        //......

        for (;;) {
            Message msg = queue.next();  //取出隊(duì)頭的消息
            if (msg == null) {
                // 如果消息為空,則跳過(guò),繼續(xù)執(zhí)行下一個(gè)message
                return;
            }
            //......
            try {
                msg.target.dispatchMessage(msg);
                //......
            } finally {
               //......
            }
           //......
            msg.recycleUnchecked();  //回收可能正在使用的消息
        }
    }

總結(jié)

消息傳遞機(jī)制簡(jiǎn)圖

image.png

面試問(wèn)題

  1. Looper 死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死,會(huì)消耗大量資源嗎?
    對(duì)于線程即是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了,線程退出。而對(duì)于主線程,我們是絕不希望會(huì)被運(yùn)行一段時(shí)間,自己就退出,那么如何保證能一直存活呢?簡(jiǎn)單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會(huì)被退出,例如,binder線程也是采用死循環(huán)的方法,通過(guò)循環(huán)方式不同與Binder驅(qū)動(dòng)進(jìn)行讀寫(xiě)操作,當(dāng)然并非簡(jiǎn)單地死循環(huán),無(wú)消息時(shí)會(huì)休眠。但這里可能又引發(fā)了另一個(gè)問(wèn)題,既然是死循環(huán)又如何去處理其他事務(wù)呢?通過(guò)創(chuàng)建新線程的方式。真正會(huì)卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時(shí)間過(guò)長(zhǎng),會(huì)導(dǎo)致掉幀,甚至發(fā)生ANR,looper.loop本身不會(huì)導(dǎo)致應(yīng)用卡死。
    主線程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢? 其實(shí)不然,這里就涉及到Linux pipe/epoll機(jī)制,簡(jiǎn)單說(shuō)就是在主線程的MessageQueue沒(méi)有消息時(shí),便阻塞在loop的queue.next()中的nativePollOnce()方法里,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往pipe管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作。這里采用的epoll機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鳎举|(zhì)同步I/O,即讀寫(xiě)是阻塞的。 所以說(shuō),主線程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源

  2. 主線程的消息循環(huán)機(jī)制是什么?
    Activity的生命周期都是依靠主線程的Looper.loop,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施:一旦退出消息循環(huán),那么你的程序也就可以退出了。 從消息隊(duì)列中取消息可能會(huì)阻塞,取到消息會(huì)做出相應(yīng)的處理。如果某個(gè)消息處理時(shí)間過(guò)長(zhǎng),就可能會(huì)影響UI線程的刷新速率,造成卡頓的現(xiàn)象。
    thread.attach(false)方法函數(shù)中便會(huì)創(chuàng)建一個(gè)Binder線程(具體是指ApplicationThread,Binder的服務(wù)端,用于接收系統(tǒng)服務(wù)AMS發(fā)送來(lái)的事件),該Binder線程通過(guò)Handler將Message發(fā)送給主線程。「Activity 啟動(dòng)過(guò)程」
    比如收到msg=H.LAUNCH_ACTIVITY,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會(huì)通過(guò)反射機(jī)制,創(chuàng)建Activity實(shí)例,然后再執(zhí)行Activity.onCreate()等方法;
    再比如收到msg=H.PAUSE_ACTIVITY,則調(diào)用ActivityThread.handlePauseActivity()方法,最終會(huì)執(zhí)行Activity.onPause()等方法。
    主線程的消息又是哪來(lái)的呢?當(dāng)然是App進(jìn)程中的其他線程通過(guò)Handler發(fā)送給主線程
    Binder用于不同進(jìn)程之間通信,由一個(gè)進(jìn)程的Binder客戶(hù)端向另一個(gè)進(jìn)程的服務(wù)端發(fā)送事務(wù),比如圖中線程2向線程4發(fā)送事務(wù);而handler用于同一個(gè)進(jìn)程中不同線程的通信,比如圖中線程4向主線程發(fā)送消息。
    ActivityThread通過(guò)ApplicationThread和AMS進(jìn)行進(jìn)程間通訊,AMS以進(jìn)程間通信的方式完成ActivityThread的請(qǐng)求后會(huì)回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會(huì)向H發(fā)送消息,H收到消息后會(huì)將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程中去執(zhí)行,這個(gè)過(guò)程就是。主線程的消息循環(huán)模型

  3. Handler 是如何能夠線程切換
    這里再引申出Handler的一些小知識(shí)點(diǎn)。 Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來(lái)構(gòu)造消息循環(huán)系統(tǒng),Looper在哪個(gè)線程創(chuàng)建,就跟哪個(gè)線程綁定,并且Handler是在他關(guān)聯(lián)的Looper對(duì)應(yīng)的線程中處理消息的。
    那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的線程中互不干擾的存儲(chǔ)并提供數(shù)據(jù),通過(guò)ThreadLocal可以輕松獲取每個(gè)線程的Looper。當(dāng)然需要注意的是①線程是默認(rèn)沒(méi)有Looper的,如果需要使用Handler,就必須為線程創(chuàng)建Looper。我們經(jīng)常提到的主線程,也叫UI線程,它就是ActivityThread,②ActivityThread被創(chuàng)建時(shí)就會(huì)初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因。

  4. 子線程有哪些更新UI的方法

  • 使用runOnUiThread()
public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

如果是在主線程,直接運(yùn)行,不是進(jìn)行mhandler.post()

  • post

new Thread() {  
    public void run() {
        button.post(new Runnable(){  
            @Override  
            public void run() {  
                button.setText("sssssss");   
            }  
        }); 
    }
}

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action); //一般情況走這里
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

實(shí)際還是調(diào)用handler

  • AsyncTask
    通過(guò)Handler

  • Handler+handleMessage

  1. 系統(tǒng)為什么不允許在子線程中訪問(wèn)UI?(摘自《Android開(kāi)發(fā)藝術(shù)探索》) 這是因?yàn)锳ndroid的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?缺點(diǎn)有兩個(gè): ①首先加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜 ②鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。 所以最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。

6.Handler使用引起的內(nèi)存泄漏原因以及解決辦法
原因

  • 當(dāng)一個(gè)android應(yīng)用程序啟動(dòng)的時(shí)候,frameworks會(huì)自動(dòng)為這個(gè)應(yīng)用程序在主線程創(chuàng)建一個(gè)Looper對(duì)象。這個(gè)被創(chuàng)建的Looper對(duì)象也有它主要的工作,它主要的工作就是不斷地處理消息隊(duì)列中的消息對(duì)象。在android應(yīng)用程序中,所有主要的框架事件(例如Activity的生命周期方法,按鈕的點(diǎn)擊事件等等)都包含在消息對(duì)象里面,然后被添加到Looper要處理的消息隊(duì)列中,主線程的Looper一直存在于整個(gè)應(yīng)用程序的生命周期中。
    當(dāng)一個(gè)Handler在主線程中被初始化。那它就一直都和Looper的消息隊(duì)列相關(guān)聯(lián)著。當(dāng)消息被發(fā)送到Looper關(guān)聯(lián)的消息隊(duì)列的時(shí)候,會(huì)持有一個(gè)Handler的引用,以便于當(dāng)Looper處理消息的時(shí)候,框架可以調(diào)用Handler的handleMessage(Message msg)。
    在java中,非靜態(tài)的內(nèi)部類(lèi)和匿名內(nèi)部類(lèi)都會(huì)隱式的持有一個(gè)外部類(lèi)的引用。靜態(tài)內(nèi)部類(lèi)則不會(huì)持有外部類(lèi)的引用。
    當(dāng)Activity被finished掉的時(shí)候,被延時(shí)的消息會(huì)在被處理之前存在于主線程的消息隊(duì)列中十分鐘,而這個(gè)消息中又包含了Handler的引用,而Handler是一個(gè)匿名內(nèi)部類(lèi)的實(shí)例,其持有外面的MainActivity的引用。這些引用會(huì)一直保持到該消息被處理,從而阻止了MainActivity被垃圾回收器回收。因此這就導(dǎo)致了MainActivity無(wú)法被回收,進(jìn)而導(dǎo)致MainActivity持有的很多資源都無(wú)法回收,這就是我們常說(shuō)的內(nèi)存泄漏。
    解決方法:
    1 有延時(shí)消息,要在Activity銷(xiāo)毀的時(shí)候移除Messages
    2 匿名內(nèi)部類(lèi)導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類(lèi),并且對(duì)上下文或者Activity使用弱引用。
最后編輯于
?著作權(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ù)。

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

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