Handler機制

我們經(jīng)常用Handler來更新UI,但是對Handler機制沒有一個系統(tǒng)的理解,現(xiàn)在做一個系統(tǒng)的解析早龟。
我們先看一張圖:



對惫霸,我們就是要理解Handler機制的三大將的關(guān)系,即Handler葱弟、Looper壹店、Message

  • Looper
    Looper主要是prepare()和loop()兩個方法。
    (1)prepare()
    prepare() {
    if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(true));
    }
    sThreadLocal是一個ThreadLocal對象芝加,可以在一個線程中存儲變量(副本)硅卢。從代碼可以看出,Looper.prepare()只能調(diào)用一次藏杖,否則報錯将塑。
    (2)loop()
    loop() {
    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; //拿到該looper實例中的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();  
    
        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  
            Printer logging = me.mLogging;  
            if (logging != null) {  
                logging.println(">>>>> Dispatching to " + msg.target + " " +  
                        msg.callback + ": " + msg.what);  
            }  
    
            msg.target.dispatchMessage(msg);  // 把消息交給msg的target的dispatchMessage方法去處理点寥,Msg的target其實是handler對象
    
            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.recycle();  
        }  
    }  
    
    public static Looper myLooper() {
        return sThreadLocal.get();
    }
    

從代碼可以看出,Looper有2個作用:
1.與當前線程綁定来吩,保證一個線程只會有一個Looper實例敢辩,同時一個Looper實例也只有一個MessageQueue。
2.loop()方法误褪,不斷從MessageQueue中去取消息责鳍,交給消息的target屬性的dispatchMessage去處理。

  • Handler
    我們看先看一下Handler的其中一個構(gòu)造函數(shù):
    public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) { // FIND_POTENTIAL_LEAKS 為 false;
    final Class klass = getClass();
    if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) {
    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
    klass.getCanonicalName());
    }
    }

        mLooper = Looper.myLooper(); // 獲取當前線程(調(diào)用者)的Looper
        if (mLooper == null) { // 如果當前線程沒有Looper兽间,則拋異常
            throw new RuntimeException( 
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; // 這里引用的MessageQueue是Looper()中創(chuàng)建的
        mCallback = callback;
        mAsynchronous = async;
    }
    

從這里我們可以看出历葛,Handler通過Looper.myLooper()獲取到當前線程的Looper對象,再通過mLooper.mQueue獲取到Looper的消息隊列嘀略,這樣Handler的實例與我們Looper實例中MessageQueue關(guān)聯(lián)上了恤溶。
我們看看怎么發(fā)消息的≈难颍看sendMessage()方法:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
再看sendMessageDelayed()方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
再看sendMessageAtTime()方法:
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);
}
再看enqueueMessage()方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
sendMessage()最終調(diào)用的是enqueueMessage()方法咒程,我們看一下方法實體。enqueueMessage中首先為meg.target賦值為this讼育,也就是把當前的handler作為msg的target屬性帐姻。最終會調(diào)用queue的enqueueMessage的方法,也就是說handler發(fā)出的消息奶段,最終會保存到消息隊列中去饥瓷。
之前已經(jīng)解釋了Looper會調(diào)用prepare()和loop()方法,在當前執(zhí)行的線程中保存一個Looper實例痹籍,這個實例會保存一個MessageQueue對象呢铆,然后當前線程進入一個無限循環(huán)中去,不斷從MessageQueue中讀取Handler發(fā)來的消息蹲缠。然后再回調(diào)創(chuàng)建這個消息的handler中的dispathMessage方法棺克,下面我們看一看這個方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我們看到調(diào)用了handleMessage()方法悠垛,其實是一個空方法:
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
為什么是一個空方法呢?因為消息的最終回調(diào)是由我們控制的娜谊,我們在創(chuàng)建handler的時候都是復寫handleMessage方法确买,然后根據(jù)msg.what進行消息處理。
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case value:
break;
default:
break;
}
};
};
總結(jié)一下
1因俐、首先Looper.prepare()在本線程中保存一個Looper實例拇惋,然后該實例中保存一個MessageQueue對象周偎;因為Looper.prepare()在一個線程中只能調(diào)用一次抹剩,所以MessageQueue在一個線程中只會存在一個。
2蓉坎、Looper.loop()會讓當前線程進入一個無限循環(huán)澳眷,不端從MessageQueue的實例中讀取消息,然后回調(diào)msg.target.dispatchMessage(msg)方法蛉艾。
3钳踊、Handler的構(gòu)造方法,會首先得到當前線程中保存的Looper實例勿侯,進而與Looper實例中的MessageQueue想關(guān)聯(lián)拓瞪。
4、Handler的sendMessage方法助琐,會給msg的target賦值為handler自身祭埂,然后加入MessageQueue中。
5兵钮、在構(gòu)造Handler實例時蛆橡,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終調(diào)用的方法掘譬。
好了泰演,總結(jié)完成,大家可能還會問葱轩,那么在Activity中睦焕,我們并沒有顯示的調(diào)用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創(chuàng)建呢靴拱,這是因為在Activity的啟動代碼中垃喊,已經(jīng)在當前UI線程調(diào)用了Looper.prepare()和Looper.loop()方法。

Handler Post方法解析

post方法并沒有新建線程缭嫡,只是發(fā)送消息:

  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;  
  } 

可以看到缔御,在getPostMessage中,得到了一個Message對象妇蛀,然后將我們創(chuàng)建的Runable對象作為callback屬性耕突,賦值給了此message.
注:產(chǎn)生一個Message對象笤成,可以new ,也可以使用Message.obtain()方法眷茁;兩者都可以炕泳,但是更建議使用obtain方法,因為Message內(nèi)部維護了一個Message池用于Message的復用上祈,避免使用new 重新分配內(nèi)存培遵。
sendMessageDelayed()方法和sendMessage()方法差不錯,原理相似登刺,只是多了個時間參數(shù)籽腕。

以上是所有Handler機制的整個原理解析,我也是跟著大神的博客理解的纸俭,嘻嘻;屎摹!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揍很,一起剝皮案震驚了整個濱河市郎楼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌窒悔,老刑警劉巖呜袁,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異简珠,居然都是意外死亡阶界,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門北救,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荐操,“玉大人,你說我怎么就攤上這事珍策⊥衅簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵攘宙,是天一觀的道長屯耸。 經(jīng)常有香客問我,道長蹭劈,這世上最難降的妖魔是什么疗绣? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮铺韧,結(jié)果婚禮上多矮,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好塔逃,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布讯壶。 她就那樣靜靜地躺著,像睡著了一般湾盗。 火紅的嫁衣襯著肌膚如雪伏蚊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天格粪,我揣著相機與錄音躏吊,去河邊找鬼。 笑死帐萎,一個胖子當著我的面吹牛比伏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吓肋,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凳怨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了是鬼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤紫新,失蹤者是張志新(化名)和其女友劉穎均蜜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芒率,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡囤耳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了偶芍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片充择。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匪蟀,靈堂內(nèi)的尸體忽然破棺而出椎麦,到底是詐尸還是另有隱情,我是刑警寧澤材彪,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布观挎,位于F島的核電站,受9級特大地震影響段化,放射性物質(zhì)發(fā)生泄漏嘁捷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一显熏、第九天 我趴在偏房一處隱蔽的房頂上張望雄嚣。 院中可真熱鬧,春花似錦喘蟆、人聲如沸缓升。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仔沿。三九已至坐桩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間封锉,已是汗流浹背绵跷。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留成福,地道東北人碾局。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像奴艾,于是被迫代替她去往敵國和親净当。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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