Handler機(jī)制 流程+源碼

前言

  • 在Android中經(jīng)常使用多線程開發(fā)偷拔,Handler則是基礎(chǔ),那么本文主要以流程+源碼的形式來記錄這種機(jī)制

Why?

為什么需要Handler?
我們需要切換線程處理UI垃帅,是因?yàn)閂iewRootImpl#checkThread()中會(huì)判斷創(chuàng)建View的線程是否與修改View的線程為同一個(gè)

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

How?

Android中是如何設(shè)計(jì)的?
使用不同的類協(xié)作完成

名稱 作用
Message 存儲(chǔ)信息數(shù)據(jù)
MessageQueue 存儲(chǔ)Message
Looper 循環(huán)/處理消息
Handler 發(fā)送和接受消息

流程圖

Handler.PNG

結(jié)合源碼

提前的預(yù)備:線程中需要提前創(chuàng)建Looper和MessageQueue
  • 創(chuàng)建Looper的兩種方式

Looper.prepare();
Looper.prepareMainLooper();
Looper的構(gòu)造方法是私有的只允許這兩種方式創(chuàng)建

  //普通的創(chuàng)建 Looper.prepare();
  private static void prepare(boolean quitAllowed) {
        //ThreadLocal封裝了一個(gè)ThreadLocalMap(key:ThreadLocal<?>,value:Obj)保存線程和Looper的信息 
        //如果獲取到了就說明這個(gè)線程有對應(yīng)的Looper對象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //將Looper和當(dāng)前線程信息保存
        sThreadLocal.set(new Looper(quitAllowed));
    }

  //主線程創(chuàng)建 Looper.prepareMainLooper(); 
  //在ActivityThread的main方法中
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

以上可看出 每個(gè)線程都只能對應(yīng)一個(gè)Looper

  • 創(chuàng)建MessageQueue
private Looper(boolean quitAllowed) {
       mQueue = new MessageQueue(quitAllowed);
       mThread = Thread.currentThread();
 }

創(chuàng)建Looper的時(shí)候就順帶創(chuàng)建了MessageQueue,所以一個(gè)線程也只有一個(gè)MessageQueue

開始循環(huán)等待消息到來

Looper.loop();

public static void loop() {
      //這里就是 final Looper me = sThreadLocal.get();
      //找到此線程對應(yīng)的Looper
      final Looper me = myLooper();
      //必須創(chuàng)建Looper
      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 (;;) {
          //MessageQueue中有消息就返回 沒消息就阻塞
          Message msg = queue.next();
          if (msg == null) {
              return;
          }
          //target是一個(gè)handler對象  此處分發(fā)了消息
          msg.target.dispatchMessage(msg);
          //回收
          msg.recycleUnchecked();
      }
   }
收到消息后的處理

將消息對接給Handler 即dispatchMessage(Message msg);

public void dispatchMessage(Message msg) {
        // callback不為空,則使用了post(Runnable r)發(fā)送消息
        // 執(zhí)行handleCallback(msg),即回調(diào)Runnable對象里復(fù)寫的run()
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //若msg.callback屬性為空恨胚,則使用了sendMessage發(fā)送消息
            // 執(zhí)行handleMessage(msg),即回調(diào)復(fù)寫的handleMessage(msg) 
            handleMessage(msg);
        }
}

Handler的創(chuàng)建

public Handler(Callback callback, boolean async) {
        ...
        //綁定對應(yīng)的looper炎咖、MessageQueue赃泡、callback
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

Handler準(zhǔn)備發(fā)送Message

發(fā)送Message有兩種方式 也就是dispatchMessage(Message msg)中用來區(qū)分的
1.handler.post(Runnable runnable);
2.handler.sendMessage(Message msg);

  • handler.post(Runnable runnable);
public final boolean post(Runnable r) {
      //先是將runnable封裝到Message中再send
      //這里后續(xù) 在第二種方式里
     return  sendMessageDelayed(getPostMessage(r), 0);
}

//這里也是把runnable當(dāng)做一個(gè)屬性賦值給Message
//之后在dispatchMessage(Message msg)時(shí) 根據(jù)callback有無判斷那種方式
private static Message getPostMessage(Runnable r) {
      Message m = Message.obtain();
      m.callback = r;
      return m;
}
  • handler.sendMessage(msg);
public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}
//第一種也是調(diào)用這個(gè)方法
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
      if (delayMillis < 0) {
          delayMillis = 0;
      }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
 }

//最終調(diào)用這個(gè):
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;
        }
        //將消息放入MessageQueue中
        return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
      //此處將Handler賦值給target
      msg.target = this;   
      if (mAsynchronous) {
          //設(shè)置消息異步,異步不會(huì)受同步障礙(某些操作會(huì)在消息隊(duì)列引入同步障礙乘盼,防止后續(xù)消息傳遞確保無效請求在恢復(fù)前得到處理)的約束
          msg.setAsynchronous(true);
      }
      return queue.enqueueMessage(msg, uptimeMillis);
 }
將消息添加到MessageQueue中

MessageQueue.enqueueMessage(Message msg, long when);

 boolean enqueueMessage(Message msg, long when) {
        //檢查消息是否有對應(yīng)的Handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //檢查消息是否已經(jīng)被使用過
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            //是否調(diào)用過MessageQueue.quit(boolean safe)方法退出
            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;
            }
            //標(biāo)記已經(jīng)使用了
            msg.markInUse();
            //消息目標(biāo)發(fā)送時(shí)間 越小優(yōu)先級越高
            msg.when = when;
            //獲取頭部消息
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //插入新的頭部升熊,如果阻塞就喚醒消息隊(duì)列
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //將消息插入隊(duì)列中
                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;
            }

            // 消息有同步屏障或者隊(duì)列中有最早的異步消息 否則不需要喚醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

這樣就把消息加入到消息隊(duì)列中了,在這之后就回到前面Looper那邊分發(fā)給Handler處理
看到這里再回頭看看流程圖和下面的總結(jié)會(huì)更好理解一些

注意要點(diǎn)

1.關(guān)于內(nèi)存泄漏

  • 采用靜態(tài)內(nèi)部類:static handler = xxx
  • Activity結(jié)束時(shí),調(diào)用handler.removeCallback()蹦肴、然后handler設(shè)置為null
  • 如果使用到Context等引用僚碎,要使用弱引用
    2.MessageQueue其實(shí)是單鏈表數(shù)據(jù)結(jié)構(gòu)主要是為了提高插入消息、刪除消息的效率
    3.一個(gè)線程最多只能有一個(gè)Looper

總結(jié)

Handler機(jī)制總結(jié).png

最后

之前看了很多次關(guān)于這方面的知識(shí)阴幌,但總是感覺斷斷續(xù)續(xù)勺阐,模模糊糊,所以這次做一個(gè)小小的總結(jié)矛双,留給以后回顧渊抽,如果文中如果有什么紕漏歡迎討論與指出。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末议忽,一起剝皮案震驚了整個(gè)濱河市懒闷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌栈幸,老刑警劉巖愤估,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異速址,居然都是意外死亡玩焰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門芍锚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昔园,“玉大人,你說我怎么就攤上這事并炮∧眨” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵逃魄,是天一觀的道長荤西。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么皂冰? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任店展,我火速辦了婚禮,結(jié)果婚禮上秃流,老公的妹妹穿的比我還像新娘赂蕴。我一直安慰自己,他們只是感情好舶胀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布概说。 她就那樣靜靜地躺著,像睡著了一般嚣伐。 火紅的嫁衣襯著肌膚如雪糖赔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天轩端,我揣著相機(jī)與錄音放典,去河邊找鬼。 笑死基茵,一個(gè)胖子當(dāng)著我的面吹牛奋构,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拱层,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼弥臼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了根灯?” 一聲冷哼從身側(cè)響起径缅,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烙肺,沒想到半個(gè)月后纳猪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桃笙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年兆旬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怎栽。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宿饱,靈堂內(nèi)的尸體忽然破棺而出熏瞄,到底是詐尸還是另有隱情,我是刑警寧澤谬以,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布强饮,位于F島的核電站,受9級特大地震影響为黎,放射性物質(zhì)發(fā)生泄漏邮丰。R本人自食惡果不足惜行您,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剪廉。 院中可真熱鬧娃循,春花似錦、人聲如沸斗蒋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泉沾。三九已至捞蚂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跷究,已是汗流浹背姓迅。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俊马,地道東北人丁存。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像潭袱,于是被迫代替她去往敵國和親柱嫌。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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