Android Handler 總結(jié)

基本概念

Handler

Handler 主要用于異步消息的處理。

Looper 溝通亿扁,Push 新消息到 MessageQueue 里,或者接收處理 LooperMessageQueue 里取出的消息药有。

Message

進行消息的封裝窗轩。

Message 類中的關(guān)鍵變量:

  1. public int what :
    用于聲明此 Message 的類型。
  2. public int arg1 :
    public int arg2 :
    用于傳遞一些整型數(shù)據(jù)介劫。
  3. public Object obj :
    用于傳遞一個對象徽惋。

Message 類中的關(guān)鍵接口方法:

  1. public static Message obtain() :
    優(yōu)先從一個全局的消息池中返回一個新的 Message(復(fù)用消息池找那個的消息)。很多情況下可以避免新生成一個消息的額外開銷座韵。(盡管可以通過 New Message() 獲得一個新的消息對象险绘,但是建議優(yōu)先使用 obtain() 獲得一個空的 Message,以節(jié)約資源)
Message msg = Message.obtain();
  1. public Handler getTarget() :
    獲取到處理此消息的 Handler 對象誉碴。
  2. public void setData(Bundle data) :
    用于傳遞一個 Bundle 對象宦棺。對應(yīng)的使用 getData()peekData() 取出此 Bundle。(注意:如果只是傳遞簡單的 int 信息黔帕,應(yīng)優(yōu)先使用 arg1代咸,arg2

MessageQueue

Looper 持有,用來保存消息(Message)成黄。
消息隊列是先進先出的呐芥。
消息不會直接添加到 MessageQueue逻杖,而是通過 Handler 對象和 Looper
可以通過 Looper.myQueue() 來獲取當前線程的 MessageQueue 對象思瘟。

Looper

Looper 字面意思就是循環(huán)者荸百。它被設(shè)計用來使一個普通線程變成 Looper 線程,也就是可以循環(huán)工作的線程潮太。
Looper 內(nèi)部維護了一個 MessageQueue 消息隊列管搪。

例如:

  class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare(); //創(chuàng)建Looper對象
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop(); //循環(huán)處理消息隊列
        }
    }

調(diào)用 quit() 方法結(jié)束 looper 循環(huán)虾攻。

Looper 有以下幾個要點:

  1. 每個線程有且只能有一個 Looper 對象铡买,它是一個 ThreadLocal
  2. Looper 內(nèi)部有一個消息隊列霎箍,loop() 方法調(diào)用后線程開始不斷從隊列中取出消息執(zhí)行奇钞。
  3. Looper 使一個線程變成 Looper 線程。

ThreadLocal

線程本地變量漂坏。
每個 Thread 內(nèi)部有一個 ThreadLocal景埃。

ThreadLocal 不是一個線程而是一個線程的本地化對象。當工作于多線程環(huán)境中的對象采用 ThreadLocal 維護變量時顶别,ThreadLocal 為每個使用該變量的線程分配一個獨立的副本谷徙。每個線程都可以獨立的改變自己的副本,而不影響其他線程的副本驯绎。

ThreadLocal 類中的接口方法:

  1. public void set(Object value) :
    設(shè)置當前線程的線程局部變量的值完慧。
  2. public Object get() :
    返回當前線程的線程局部變量的值。
  3. public void remove() :
    刪除當前線程的局部變量的值剩失。
  4. protected Object initialValue() :
    返回當前線程局部變量的初始值屈尼。

ThreadLocal 是如何做到為每一個線程維護一份獨立的變量副本的呢?
思路很簡單拴孤,在 ThreadLocal 類中有一個 Map, Map中的鍵為線程對象脾歧,值為對應(yīng)線程的變量副本。

ThreadLocal 與線程同步機制的比較:

線程同步機制通過對象的鎖機制保證同一時間只有一個線程去訪問變量演熟,該變量時多個線程共享的鞭执。ThreadLocal 則為每一個線程提供了一個變量副本,從而隔離了多個線程訪問數(shù)據(jù)的沖突芒粹,ThreadLocal 提供了線程安全的對象封裝蚕冬,在編寫多線程代碼時,可以把不安全的代碼封裝進 ThreadLocal是辕。概括的說囤热,對于多線程資源共享的問題,線程同步機制采取了時間換空間的方式获三,訪問串行化旁蔼,對象共享化锨苏;而 ThreadLocal 采取了空間換時間的方式,訪問并行化棺聊,對象獨享化伞租。

使用例子:

public class TestThreadLocal {

    private ThreadLocal<Integer> mNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            //設(shè)置默認值為0
            return 0;
        }
    };

    public int getNextNum() {
        mNum.set(mNum.get() + 1);
        return mNum.get();
    }

    private static class TestThread extends Thread {
        private TestThreadLocal mTest;

        private TestThread(TestThreadLocal testThreadLocal) {
            mTest = testThreadLocal;
        }

        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println("Thread name:" + Thread.currentThread().getName() + ",num:" + mTest.getNextNum());
            }
        }
    }

    public static void main(String [] args) {
        TestThreadLocal testThreadLocal = new TestThreadLocal();
        TestThread thread1 = new TestThread(testThreadLocal);
        TestThread thread2 = new TestThread(testThreadLocal);
        TestThread thread3 = new TestThread(testThreadLocal);
        thread1.start();
        thread2.start();
        thread3.start();
    }

}

打印

Thread name:Thread-0,num:1
Thread name:Thread-1,num:1
Thread name:Thread-2,num:1
Thread name:Thread-1,num:2
Thread name:Thread-0,num:2
Thread name:Thread-2,num:2
Thread name:Thread-0,num:3
Thread name:Thread-2,num:3
Thread name:Thread-1,num:3

Handler 的原理

Handler 原理圖

Handler 原理:

  1. Handler 關(guān)聯(lián)線程的 Looper
  2. Handler 發(fā)送消息限佩,通過 LooperMessageQueue 把消息插入 MessageQueue 隊列中葵诈。
  3. Looper 不斷循環(huán)份帐,取出 MessageQueue 中隊頭的 Message辐啄。
  4. 調(diào)用 HandlerdispatchMessage 方法,讓 Handler 處理消息霞势。

Handler 和 Looper

Handler 的初始化主要有兩種方式:

  1. 未指定 Looper方式:
Handler handler = new Handler(); 

通過這種方式 new 出來的 Handler 對象晕城,默認使用當前線程的 Looper泞坦。
源碼如下:

public Handler() {
     this(null, false);
}
    
public Handler(Callback callback, boolean async) {
     ...

     mLooper = Looper.myLooper(); // 默認將關(guān)聯(lián)當前線程的looper
     if (mLooper == null) {
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
      mQueue = mLooper.mQueue; //關(guān)聯(lián)looper的MQ作為自己的MQ,因此它的消息將發(fā)送到關(guān)聯(lián)looper的MQ上
      mCallback = callback;
     mAsynchronous = async;
}

注意:
Activity 被創(chuàng)建時就默認創(chuàng)建了 Looper砖顷,Thread 是沒有默認創(chuàng)建 Looper 的贰锁。

  1. 指定 Looper 的方式:
public Handler(Looper looper) {
    this(looper, null, false);
}
    
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper; //直接設(shè)置為傳入的 Looper 對象
    mQueue = looper.mQueue; //關(guān)聯(lián)的也是傳入的 Looper 中的 MQ
    mCallback = callback;
    mAsynchronous = async;
}

注意:
一個線程可以有對個 Handler,但是只能有一個 Looper滤蝠。

Handler 發(fā)送消息

Handler 的使用會有兩種方式豌熄,一種是發(fā)送 Message 、一種是發(fā)送 Runnable物咳。

  1. 發(fā)送 Message

這種方式有以下接口方法:

sendEmptyMessage(int)

sendMessage(Message)

sendMessageAtTime(Message, long)

sendMessageDelayed(Message, long)

幾個接口調(diào)用如下圖:

發(fā)送消息接口

可見最后都是調(diào)用的 sendMessageAtTime(Message msg, long uptimeMillis) 接口锣险。

public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {
        //重要,這里把當前Handler設(shè)置給msg.target所森,方便后面從MQ中取出消息后囱持,能讓對應(yīng)的Handler處理此消息
        msg.target = this; 
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //把消息放入MQ消息隊列
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  1. 發(fā)送 Runnable
    這種方式有以下接口方法:
post(Runnable)

postAtTime(Runnable, long)

postDelayed(Runnable, long)

幾個接口的調(diào)用如下圖:

發(fā)送Runnable調(diào)用圖

由上圖可見,最后都會調(diào)用到 sendMessageDelayed(Message msg, long delayMillis) 接口焕济,而 Runnable 者會封裝到 Messagecallback 變量中纷妆,而 Message 也是放入到消息隊列中的。

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

Looper 中對消息的處理

調(diào)用 Looperloop() 方法后晴弃,Looper 線程就開始了循環(huán)工作掩幢,不斷的從 MessageQueue 中取出隊頭的消息執(zhí)行。

public static void loop() {
        //獲取當前線程的Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //獲取當前Looper的MessageQueue
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        //進入死循環(huán)
        for (;;) {
            //從消息隊列取出隊頭的消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //關(guān)鍵:msg.target為發(fā)送消息的Handler上鞠,這里把取出來的msg交給Handler處理
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

Handler 中 dispatchMessage 處理消息

上面 Looper 取出消息后际邻,會調(diào)用 HandlerdispatchMessage(Message msg) 接口處理消息。

public void dispatchMessage(Message msg) {
        //msg.callback 為 Runnable芍阎,即Handler.post(Runnable r) 這種方式調(diào)用的
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        //如果不是post Runnable 的方式世曾,則為sendMessage的方式,直接會調(diào)用handleMessage(msg)處理消息
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
private static void handleCallback(Message message) {
        //post runnable 的方式谴咸,handleCallback中直接調(diào)用了Runnable的run方法
        message.callback.run();
    }

關(guān)于線程問題

由于 Handler 是在它關(guān)聯(lián)的 Looper 線程中處理(dispatchMessage(Message msg))消息的轮听,所以 Looper 所在的線程決定了 Handler 的處理消息的線程骗露。

Activity 的主線程也是一個 Looper 線程,所以在主線程中創(chuàng)建的 Handler血巍,在子線程中通過 Handler 發(fā)送消息萧锉,最后處理消息時在主(UI)線程中。這也是我們平時為什么能通過 Handler 來解決非主線程中更新 UI 的問題的原因述寡。


如何讓 Handler 關(guān)聯(lián)一個子線程的 Looper柿隙,使 Handler 在子線程中處理消息?
Android HandlerThread





參考:

android的消息處理機制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲫凶,一起剝皮案震驚了整個濱河市禀崖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掀序,老刑警劉巖帆焕,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惭婿,死亡現(xiàn)場離奇詭異不恭,居然都是意外死亡,警方通過查閱死者的電腦和手機财饥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門换吧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钥星,你說我怎么就攤上這事沾瓦。” “怎么了谦炒?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵贯莺,是天一觀的道長。 經(jīng)常有香客問我宁改,道長缕探,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任还蹲,我火速辦了婚禮爹耗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谜喊。我一直安慰自己潭兽,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布斗遏。 她就那樣靜靜地躺著山卦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诵次。 梳的紋絲不亂的頭發(fā)上账蓉,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天炫狱,我揣著相機與錄音,去河邊找鬼剔猿。 笑死视译,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的归敬。 我是一名探鬼主播酷含,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汪茧!你這毒婦竟也來了椅亚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤舱污,失蹤者是張志新(化名)和其女友劉穎呀舔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扩灯,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡媚赖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了珠插。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惧磺。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捻撑,靈堂內(nèi)的尸體忽然破棺而出磨隘,到底是詐尸還是另有隱情,我是刑警寧澤顾患,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布番捂,位于F島的核電站,受9級特大地震影響江解,放射性物質(zhì)發(fā)生泄漏设预。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一膘流、第九天 我趴在偏房一處隱蔽的房頂上張望絮缅。 院中可真熱鬧,春花似錦呼股、人聲如沸耕魄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吸奴。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間则奥,已是汗流浹背考润。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留读处,地道東北人糊治。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像罚舱,于是被迫代替她去往敵國和親井辜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容