Handler學習筆記

目錄

學習目錄

1. Handler的作用

  • 簡單說Handler用于同一個進程的線程之間通信。(可以是不同線程,也可以是同一個線程,比如像做延時操作)

  • 基本原理是,Handler發(fā)送Message,并放入對應線程的MessageQueue中,Looper讓對應線程無限循環(huán)地從自己的MessageQueue拿出消息處理。(Handler和Looper持有的是同一個MessageQueue)

  • 使用的最多的場景就是,我們在UI線程創(chuàng)建好Handler實例,然后在子線程做完耗時操作后,想要更新UI內(nèi)容時,通過mHander sendMessage通知UI更新。而正如1所說,理論上我們也完全可以由UI線程發(fā)送Message,由子線程接收并處理,只是比較少見。另一個多見的場景是延時任務,往往是UI線程自己跟自己通信。

2. 為什么需要Handler?

  • 一般來說,我們只要在子線程把信息放進主線程的MessageQueue里就可以了。因為,在同一進程中線程和線程之間資源是共享的,也就是對于任何變量在任何線程都是可以訪問和修改的,只要考慮并發(fā)性做好同步就行了,那么只要拿到主線程的MessageQueue 的實例,就可以放入消息,主線程的Looper在輪詢MessageQueue時,就可以取出該消息并處理。

  • 主線程的MessageQueue的實例是可以拿到的(在主線程下 Looper.myLooper().mQueue),但是Google 為了統(tǒng)一添加消息和消息的回調(diào)處理,又專門構建了Handler類.只要在主線程構建Handler類,那么這個Handler實例就獲取主線程MessageQueue實例的引用,Handler 在sendMessage的時候就通過這個引用往消息隊列里插入新消息。

  • Handler 的另外一個作用,就是能統(tǒng)一處理消息的回調(diào)。這樣一個Handler發(fā)出消息又確保消息處理也是自己來做,這樣的設計非常的贊。具體做法就是在隊列里面的Message持有Handler的引用(哪個handler 把它放到隊列里,message就持有了這個handler的引用),然后等到主線程輪詢到這個message的時候,就來回調(diào)我們經(jīng)常重寫的Handler的handleMessage(Message msg)方法。

// 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.");
        }
        // 獲取MessageQueue
        final MessageQueue queue = me.mQueue;
        // 省略
        // 具體的輪詢邏輯,無限for循環(huán)
        for (;;) {
        // 取出Message
            Message msg = queue.next(); // might block
            // 省略
            try {
            // target為發(fā)送message的Handler實例
            // Handler處理
                msg.target.dispatchMessage(msg);
            } 
            // 省略
        }
    }

所以說,引入Handler只是為了大家使用方便以及代碼的清晰簡潔。并沒有大家想的那么高深。


3. 具體的使用

3.1 主線程使用Handler刷新UI

Handler handler = new Handler()

實際會調(diào)用

 public Handler(Callback callback, boolean async) {
        // 省略
        // 這里也驗證了,Handler在哪個線程創(chuàng)建,他就會持有哪個線程的Looper
        // 我們一般在UI線程初始化,Handler就會持有UI線程的Looper和MessageQueue
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 這里也驗證了,Handler在哪個線程創(chuàng)建,他就會持有哪個線程的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

在其他線程創(chuàng)建想在主線程處理事情的Handler,可以用以下代碼可以達到相應效果

// 傳入UI線程的Looper
Handler handler = new Handler(Looper.getMainLooper);

3.2 LooperThread子線程使用(官網(wǎng)的文檔)

class LooperThread extends Thread {
       //其他線程可以通過mHandler這個引用給該線程的消息隊列添加消息
       public Handler mHandler;
       public void run() {
            Looper.prepare();
            //需要在線程進入死循環(huán)之前,創(chuàng)建一個Handler實例供外界線程給自己發(fā)消息
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    //Handler 對象在這個線程構建,那么handleMessage的方法就在這個線程執(zhí)行
                }
            };
            // loop方法里會用到Handler實例
            // 所以必須先初始化Handler
            // 如果在loop方法之后初始化Handler,那么loop方法執(zhí)行中會報錯
            Looper.loop();
            // loop之后才初始化Handler,代碼是無效的,loop是死循環(huán),正常情況下這行代碼就不會執(zhí)行了
            // mHandler = new Handler()......
        }
    }

需要說明的是,上面寫到的Looper.prepare,創(chuàng)建Handler和Looper.loop方法的順序并不一定不能改。如果你想的話,也完全可以loop執(zhí)行之后創(chuàng)建Handler,只是創(chuàng)建的流程不能寫在loop后面。因為loop里的死循環(huán)會導致你的代碼不執(zhí)行,你可以在主線程通過LooperThread.mHander這樣的引用,來創(chuàng)建實例,效果也是一樣的。

注意,其實UI線程也有類似的代碼,如下:

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

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

與上面的例子類似,系統(tǒng)在這里也為我們初始化了一個Handler。我們每次使用Handler mHandler = new Handler();都是額外創(chuàng)建了一個Handler,與原有的不沖突。msg.target.dispatchMessage(msg)這句代碼會判斷target。


4.Handler發(fā)送消息的兩種方式

Handler,它直接繼承自Object,一個Handler允許發(fā)送和處理Runnable或者Message對象,并且會關聯(lián)到主線程的MessageQueue中。所以Handler把消息壓入MessageQueue也有兩種方式,post(new Runnable)和sendMessage(Message msg)。

4.1 post

post允許把一個Runnable對象入隊到消息隊列中。它的方法有:

  • post(Runnable)
  • postAtTime(Runnable,long)
  • postDelayed(Runnable,long)。

4.2 sendMessage

sendMessage允許把一個包含消息數(shù)據(jù)的Message對象壓入到消息隊列中。它的方法有:

  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message,long)
  • sendMessageDelayed(Message,long)。

從上面的各種方法可以看出,不管是post還是sendMessage都具有多種方法,它們可以設定Runnable對象和Message對象被入隊到消息隊列中,是立即執(zhí)行還是延遲執(zhí)行。

4.3 post和sendMessage方法的聯(lián)系和區(qū)別

先看源碼

public final boolean post(Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

代碼很好理解,post方法其實還是把Runnable對象轉(zhuǎn)化成了Message,區(qū)別在于普通的sendMessage不會使用callBack參數(shù),它具體的處理邏輯在Handler的handleMessage里。而post會使用Message的callback,callback就是Runnable對象,所以使用post方法的話,無需再去寫具體的handleMessage邏輯。源碼如下:

// dispatchMessage方法是在Looper.loop開啟循環(huán),開始處理MessageQueue里的每個Message時調(diào)用的,可以發(fā)現(xiàn),默認先調(diào)用callback,沒有callback才會使用handleMessage
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {                        
            return;
            }
        }
        handleMessage(msg); 
    }
}

5. 了解Message

Message是一個final類,所以不可被繼承。Message封裝了線程中傳遞的消息,如果對于一般的數(shù)據(jù),Message提供了getData()和setData()方法來獲取與設置數(shù)據(jù),其中操作的數(shù)據(jù)是一個Bundle對象,這個Bundle對象提供一系列的getXxx()和setXxx()方法用于傳遞基本數(shù)據(jù)類型的鍵值對,對于基本數(shù)據(jù)類型,使用起來很簡單,這里不再詳細講解。而對于復雜的數(shù)據(jù)類型,如一個對象的傳遞就要相對復雜一些。在Bundle中提供了兩個方法,專門用來傳遞對象的,但是這兩個方法也有相應的限制,需要實現(xiàn)特定的接口,當然,一些Android自帶的類,其實已經(jīng)實現(xiàn)了這兩個接口中的某一個,可以直接使用。方法如下:

putParcelable(String key,Parcelable value):需要傳遞的對象類實現(xiàn)Parcelable接口。

pubSerializable(String key,Serializable value):需要傳遞的對象類實現(xiàn)Serializable接口。

還有另外一種方式在Message中傳遞對象,那就是使用Message自帶的obj屬性傳值,它是一個Object類型,所以可以傳遞任意類型的對象,Message自帶的有如下幾個屬性:

int arg1:參數(shù)一,用于傳遞不復雜的數(shù)據(jù),復雜數(shù)據(jù)使用setData()傳遞。

int arg2:參數(shù)二,用于傳遞不復雜的數(shù)據(jù),復雜數(shù)據(jù)使用setData()傳遞。

Object obj:傳遞一個任意的對象。

int what:定義的消息碼,一般用于設定消息的標志。

注意

對于Message對象,一般并不推薦直接使用它的構造方法得到,而是建議通過使用Message.obtain()這個靜態(tài)的方法或者Handler.obtainMessage()獲取。Message.obtain()會從消息池中獲取一個Message對象,如果消息池中是空的,才會使用構造方法實例化一個新Message,這樣有利于消息資源的利用。并不需要擔心消息池中的消息過多,它是有上限的,上限為10個。Handler.obtainMessage()具有多個重載方法,如果查看源碼,會發(fā)現(xiàn)其實Handler.obtainMessage()在內(nèi)部也是調(diào)用的Message.obtain()。

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

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

  • 前言 在Android開發(fā)的多線程應用場景中,Handler機制十分常用 今天,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 530評論 0 0
  • 1. 前言 在之前的圖解Handler原理最后留下了幾個課后題,如果還沒看過那篇文章的,建議先看那篇文章,課后題如...
    唐江旭閱讀 6,085評論 5 45
  • 異步消息處理線程啟動后會進入一個無限的循環(huán)體之中,每循環(huán)一次,從其內(nèi)部的消息隊列中取出一個消息,然后回調(diào)相應的消息...
    cxm11閱讀 6,525評論 2 39
  • 【Android Handler 消息機制】 前言 在Android開發(fā)中,我們都知道不能在主線程中執(zhí)行耗時的任務...
    Rtia閱讀 5,091評論 1 28
  • 在最后一期《向往的生活》中,謝娜和趙麗穎做客“蘑菇屋”,娜姐無意間提到,“麗穎一年只休息了三天!” 365天只休息...
    無尾熊自成長閱讀 496評論 0 3

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