Handler機制詳解

前言

handler是Android中獨特的線程間通信方式.我們使用handler的經典模式是,在主線程中創建一個handler并重寫其handlerMessage方法,然后在子線程中發送消息讓handler進行處理.下面,就來看看這個handler里面到底是怎么實現的.
@TODO:handler經典用法

創建Handler

//Handler的構造函數
public Handler(Callback callback, boolean async) {
        .....代碼省略...
       mLooper = Looper.myLooper();    //取出looper對象
        if (mLooper == null) {        //判斷looper是否為空
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");//
        }
        mQueue = mLooper.mQueue;    //取出looper的MessageQueue對象
        mCallback = callback;
        mAsynchronous = async;
    }

其中,myLooper()方法,最終調用了sThreadLocal.get()方法,從sThreadLocal中取出Looper.
sThreadLocal是一個ThreadLocal對象,它可以保證每個線程中只有一個Looper對象.關于ThreadLocal具體可以參考這篇文章.

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

接下來,判斷mLooper對象是否為空,為空則拋出異常:"如果沒有調用Looper.prepare()方法,則不能創建Handler"
為什么呢?我們一般在主線程創建handler的時候,并沒有調用過prepare方法,

//Looper類
 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    private static void prepare(boolean quitAllowed) {
        .....代碼省略....
        sThreadLocal.set(new Looper(quitAllowed));
    }
//ActivityyThread 類
public static final void main(String[] args) {
        SamplingProfilerIntegration.start();
       ……
        Looper.prepareMainLooper();
}

那為什么沒有拋出這個異常呢?
我們看到ActivityThread中,已經調用了looper.prepareMainLooper()方法創建一個looper對象并放到容器sThreadLocal中.
另外,在創建looper的同時創建了一個消息隊列.

創建消息

一般通過message.obtain(),從message pool中,取出復用的空消息,并給msg設置相應的數據obj,標識what,when等屬性.
message主要是對我們發送的消息進行此一次封裝.

handler發送消息到消息隊列

創建完消息后,我們會調用handler.sendMessage()方法,將消息發送到消息隊列,而sendmessage方法最終調用enqueueMessage方法,
首先調用msg.target = this;把當前的handler對象賦值給msg的target,這里就實現了handler與msg的綁定,后續在消息分發給handler處理的時候,就知道該由哪個handler來處理了.
至于enqueueMessage方法,主要是將消息列表里面的第一條消息取出,進行時間的對比排序.

public final boolean sendMessageAtFrontOfQueue(Message msg) {
        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, 0);
    }
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Looper輪詢器

剛才在ActivityThread中,在Looper.prepareMainLooper()方法之后,還調用了Looper.loop()方法
為什么突然講到這個呢?我們將消息放到請求隊列之后,需要調用looper的loop方法,不斷從消息隊列中取出消息,并分發給對應的handler來執行.

public static void loop() {
        final Looper me = myLooper();
        ......
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
            Printer logging = me.mLogging;

            msg.target.dispatchMessage(msg);//分發消息給對應的handler
    }
//Handler類:
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);    //這里就是我們重寫的handleMessage方法
        }
    }

Loop()方法中的主要邏輯是不斷從消息隊列中取出消息,并將消息分發給對應的handler.
然后會調用handler的dispatchMessage方法,處理消息.至此,全過程過程完成.
注意了,當我們在子線程中創建handler時,除了需要手動調用Looper.prepare()方法之外,還需要調用Looper.loop()方法開啟輪詢.

異步消息處理機制

Handler進行消息處理的模式,總體來說如下:
1.Handler發送消息到消息隊列MessageQueue,并創建Looper開啟輪詢,
2.Looper對象不停遍歷MessageQueue,從消息隊列中取出消息.分發給對應的handler
3.handler獲得消息后,調用handleMessage方法處理消息.

擴展: HandlerThread

@HydraTeam 出品 轉載請注明出處

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容