再來億遍 一遍帶你搞懂Android Handler機制

目錄

  • Handler的做用和簡單使用
  • 從源碼看Handler的原理
  • 一些知識點的整理
  • 總結

Handler的做用和簡單使用

相信Handler對於做爲Android開發者的小夥伴們來講並不陌生,某些階段的面試中Handler甚至是必考題之一。那麼Handler到底有什麼用呢?爲何要用Handler呢?java

咱們都知道在Android中分有UI線程和非UI線程,其中有一條規定就是隻能在UI線程操做UI組件,這是爲了防止多個線程併發的操做一個UI組件帶來的問題。同時咱們也知道不能在UI線程中作耗時操做,那麼這個時候就帶來了問題,若是咱們要在一個耗時操做結束後再去操做某個UI組件,那要怎麼作呢?好比咱們須要下載一份文件,而且在文件下載完成以後須要讓TextView顯示下載完成來提醒用戶。面試

這個時候就輪到咱們的Handler出場了:緩存

public class MainActivity extends AppCompatActivity {

    TextView tv;

    private static final int MSG_DOWNLOAD_FINISH=10;


    //建立Handler,同時會發現as會提示不建議咱們這麼寫,這個稍後再說
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MSG_DOWNLOAD_FINISH:
                    tv.setText("下載成功!");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv=findViewById(R.id.tv);
        tv.setText("downloading ...");
        
        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    //這裏模擬一個耗時操做,能夠看到這裏是在非UI線程運行的
                    Thread.sleep(3000);
                    
                    Message message = handler.obtainMessage(MSG_DOWNLOAD_FINISH);
                    message.sendToTarget();
                    
                    //上面部分也能夠這麼寫,效果是同樣的
// Message message=new Message();
// message.what=MSG_DOWNLOAD_FINISH;
// handler.sendMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
複製代碼

運行上面的代碼咱們能夠發如今子線程sleep 3s後,成功的修改了tv的文字爲 下載成功。咱們成功的知足了Android對UI線程的兩條規定,咱們憑藉Handler成功的在子線程作耗時操做而且在完成以後更新UI界面。數據結構

到此咱們否則發現Handler能夠作線程直接的通訊使用,經過Handler發送的一個Message,UI線程就能收到子線程要傳達的消息,那麼Handler爲何能作到線程間的通訊呢?併發

從源碼看Handler的原理

咱們先來對Handler的工做流程有一個總體的瞭解less

假設有AB兩個線程。在A線程建立一個Handler對象h,而且把h發送給B線程。此時A線程中經過Looper.loop()達到死循環不停的從A線程的MessageQueue中嘗試獲取一個Message,若是獲取到Message就會經過message.target獲取到A線程中建立的h,並調用h.dispatchMessage()方法(注意這一部分都是在A線程中進行的)。這時B線程獲取到h對象和經過h.sendMessage(msg)將一個msg插入到了A線程的MessageQueue中,這樣子一個流程就完成了。async

再看源碼以前咱們先說下Handler的總體結構,話很少說看圖。 ide

能夠看到主要涉及到三個類函數

  • Handler類,Handler類內部會持有一個MessageQueue對象和一個Looper的實例,這兩個實例都是和線程相關的,在調用Handler構造函數而且沒傳入Looper參數的狀況下,默認就是當前線程的Looper和MessageQueue
  • Looper類,經過Looper.prepare()和Looper.prepareMainLooper()這兩個靜態方法構建實例,後者是用來初始化Main線程(也就是UI線程)的Looper的,咱們在應用開發中不要使用這個方法,會報錯哦。
  • MessageQueue類。一個鏈表實現的消息隊列,由Looper負責初始化並被當前Looper對象持有。由於Looper對象是和某個線程綁定的,因此MessageQueue也是和線程綁定的。即一個線程只能有一個Looper和MessageQueue實例,而且不一樣線程的Looper,MessageQueue不一樣。

值得注意的是線程不是一開始就擁有和本身綁定的Looper,MessageQueue的,在使用Handler以前須要咱們去調用Looper.prepare()方法來初始化當前線程的Looper,MessageQueue對象。咱們能夠看下這個方法作了什麼事情:oop

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

能夠看到Looper.prepare方法最終會構建一個Looper對象並放入ThreadLocal中。關於ThreadLocal小夥伴們能夠簡單理解爲不一樣線程從同一個ThreadLocal中得到的對象是不一樣的,是獨屬於本身線程的。

經過上述的代碼咱們也可以發現同一個線程只能擁有一個Looper對象。 看到這裏的小夥伴可能會奇怪了,在咱們上面的簡單使用中咱們並無調用Looper.prepare(),爲何還能使用Handler呢?

這是應爲Android系統在啓動當前APP建立出UI線程後就會去執行Looper.prepareMainLooper()方法,系統已經幫咱們初始化過了UI線程的Looper,因此咱們能夠直接使用Handler對象了。

而說了Looper.prepare()就不得不提下Looper.loop()方法了。事實上咱們要想在子線程成功的new 出Handler而且順利使用的話,必需要再調用下Looper.loop()方法。 loop方法是一個是創建一個死循環,不停的嘗試從當前的MessageQueue中獲取一個Message。

Looper.loop()

public static void loop() {

        //myLooper()就是經過ThreadLocal獲取當前線程的Looper實例
        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 (;;) {

            /** * 經過MessageQueue獲取一個msg。這裏小夥伴會問了不是說好了是循環的嗎? * 可是這裏若是queue.next()返回爲空不就return掉了嗎? * 這裏大可放心,queue.next()內部又是一個死循環,只有當如下狀況是纔會返回空 * 1.調用MessageQueue的quite方法 * 2.Application某些狀況下嘗試去重啓looper(這部分存疑) * 博主只發現這兩個狀況,有其餘狀況歡迎指出 */

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

            //....
            try {
                //msg.target就是發送該msg的Handler對象
                /** * 調用Handler的dispatchMessage分爲三種狀況 * 1.當msg含有Callback時候會調用msg的callback * 2.當msg不含有Callback可是Handler有設置Callback時候會調用Handler的callback。若是Handler的callback返回爲true的話就不會在執行handleMessage方法 * 3.不知足以上二者時會調用Handler的handleMessage方法,也就是咱們例子中實現的方法 */
                msg.target.dispatchMessage(msg);
            } finally {
                //...
            }
            //...
            
            //這裏可見Message在使用以後會被清空數據並緩存
            msg.recycleUnchecked();
        }
    }
複製代碼

調用Handler的dispatchMessage的三種狀況

  • 1.當msg含有Callback時候會調用msg的callback
  • 2.當msg不含有Callback可是Handler有設置Callback時候會調用Handler的callback。若是Handler的callback返回爲true的話就不會在執行handleMessage方法
  • 3.不知足以上二者時會調用Handler的handleMessage方法,也就是咱們例子中實現的方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        //這裏的callback是一個Runnable。
        //調用handler.post(new Runnable())方法就是生成一個帶callback的msg並投入到MessageQueue中
        message.callback.run();
    }
複製代碼

handler.sendMessage(msg)

看完了取出Message並處理的操做,咱們看看發送Message部分的邏輯。 sendMessage(msg) ->sendMessageDelayed(msg, 0) ->sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) ->enqueueMessage(queue, msg, uptimeMillis) ->queue.enqueueMessage(msg, uptimeMillis) 能夠看到上面這一串調用鏈以後最終會調用MessageQueue的enqueueMessage方法。而這上面這一串調主要作了一些常規的檢測操做,同時把當前的Handler賦值給msg.target。這部分很少說,咱們重點看入隊操做

boolean enqueueMessage(Message msg, long when) {
        //常規性檢測,注意下msg.isInUse判斷,表明一個msg只能入隊一次
        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) {
            //若是調用過stop,此時判斷就會爲true
            if (mQuitting) {
                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的整套工做流程了。關於Handler中的線程切換,若是有點迷糊的話能夠這麼想 無論Handler被傳遞了什麼線程,不論是在什麼線程發送的消息。最終對該消息的處理都是在最初建立Handler的線程上。

一些知識點的整理

爲何主線程中使用Handler不須要初始化Looper

由於Android系統在啓動APP的時候已經調用過Looper.prepareMainLooper();和Looper.loop()了 ActivityThread.java的main方法

public static void main(String[] args){
    ...
    Looper.prepareMainLooper(); 
    //初始化Looper
    ...
    ActivityThread thread = new ActivityThread();
    //實例化一個ActivityThread
    thread.attach(false);
    //創建Binder通道
    ... 
    Looper.loop();
    //主線程進入無限循環狀態,等待接收消息
}
複製代碼

爲何主線程中Looper.loop()開啓死循環不會形成APP無響應

這部分參考知乎上的一個答案 Android中爲何主線程不會由於Looper.loop()裏的死循環卡死?

Handler的內存泄漏

這個問題我在最初的例子中寫到了,as不建議咱們這麼寫Handler,這是應爲非靜態內部類會持有外部內的引用。那麼Handler將會持有Activity的引用,咱們知道handler是會被msg.target持有的,而msg又在MessageQueue隊列中,那麼當消息隊列中擁有未消費的Message時,會致使Activity即便finish了也沒法被GC回收,最終致使內存泄漏。爲了不這個問題咱們能夠將Handler寫成外部內或者靜態的內部類,而且傳遞的Activity引用能夠用WeakReference弱引用來持有,同時能夠在Activity的onDestory中使用Handler.removeCallbacksAndMessages(null);來清空消息隊列

總結

因爲Handler還有一部分涉及到native層面,而對這一層面博主並不瞭解,因此沒有能提到這部分的東西,但願之後能有時間補充這部分的內容。以上內容如有錯誤之處歡迎你們指出,你們一塊兒進步。

相關文章
相關標籤/搜索