Handler需要知道的知識(shí)點(diǎn)

1.Handler存在的意義

對(duì)于我們而言主要是用于刷新UI饥努,因?yàn)樽泳€程不能刷新UI,如果你對(duì)handler有充分的了解,還是可以用到其他地方的绒北,鴻洋大神有篇博客是用handler加載圖片的吃引,不妨去研究一番。

2.掌握Handler一些必備知識(shí)點(diǎn)

要想實(shí)現(xiàn)子線程通知Handler刷新UI是比較簡單的妇汗,你只要看下別人的寫法自己就能模仿寫出來,寫多幾次就能無腦的寫出來了说莫,我以前就是這么的無腦操作使用Handler刷新UI的杨箭。但是,知恥而后勇的我良心發(fā)現(xiàn)储狭,這么無腦不行啊互婿,決定去看下各位大神對(duì)Handler的講解,總算有一番了解辽狈。
不扯淡慈参,Handler、Looper刮萌、MessageQueue三個(gè)是一起工作的驮配,以我的表達(dá)能力根本不用畫圖就能講清他們之間的關(guān)系,先從三個(gè)單詞了解它們着茸,Handler是處理者的意思壮锻,MessageQueue是消息隊(duì)列的意思,Looper循環(huán)的意思涮阔,他們之間的關(guān)系很簡單猜绣,Handler有幾個(gè)方法可以發(fā)送信息(當(dāng)我們的子線程需要通知刷新UI的時(shí)候就這么做)到MessageQueue,Looper的loop()方法一直循環(huán)讀取MessageQueue里面的消息敬特,然后將消息分發(fā)給對(duì)應(yīng)的Handler掰邢,一般是把信息交給Handler的HandlerMessage()方法,所以我們?cè)贖andlerMessage()處理消息(比如拿到的數(shù)據(jù)顯示在對(duì)應(yīng)的view上面)伟阔,這是一個(gè)大概的流程辣之,代碼展示大概是如下幾步:

3.從源碼才能更加了解更多的細(xì)節(jié)(涉及到Handler、Looper皱炉、MessageQueue怀估、Message的源碼)

  • 1.在UI線程中創(chuàng)建兩個(gè)Handler(我們這里講的Handler、Looper娃承、MessageQueue都是屬于UI線程的)

    private Handler mHandler1 = new Handler() {
    
      @Override
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
          LLog.d("handleMessage", "" + msg.what);
      }
    } ;
    
    private Handler mHandler2 = new Handler() {
    
      @Override
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
          LLog.d("handleMessage", "" + msg.what);
      }
    };
    
  • 2.在子線程中使用mHandler1和mHandler2發(fā)送消息到同一個(gè)MessageQueue(主線程始終只有一個(gè))

     new Thread() {
    
          @Override
          public void run() {
              super.run();
              //在子線程中使用UI線程的Handler將想要發(fā)送的信息發(fā)送到MessageQueue中
              //Looper會(huì)一直從MessageQueue拿到信息交給handleMessage()奏夫,所以我們只需要在handleMessage()
              //對(duì)消息進(jìn)行處理即可怕篷,其實(shí)主線程只有一個(gè)MessageQueue
              mHandler1.sendEmptyMessage(1);
    
              mHandler2.sendEmptyMessage(2);
          }
      }.start();
    

為什么sendEmptyMessage()之后历筝,消息就來到了handleMessage(Message msg)呢酗昼?
因?yàn)閚ewHandler()和sendEmptyMessage()方法隱蔽了很多的細(xì)節(jié),所以我們需要打開Handler的源碼才能知道流程梳猪,請(qǐng)看源碼:

public Handler() {  
    this(null, false);  
}  
public Handler(Callback callback, boolean async) {  
    if (FIND_POTENTIAL_LEAKS) {  
        final Class<? extends Handler> 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();      //拿到當(dāng)前線程(UI線程)的Looper()實(shí)例
    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;  
}  

是的麻削,看注釋我們發(fā)現(xiàn)在這里我們拿到了一個(gè)需要的Looper實(shí)例,前面說過需要Looper的loop()方法不斷的從MessageQueue中不斷獲取Message(消息)春弥,所以必須要有一個(gè)Looper實(shí)例呛哟,不急著看Handler的其他源碼,先點(diǎn)進(jìn)這里L(fēng)ooper.myLooper()看下是如何獲取到Looper實(shí)例的匿沛。

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();   
}

Looper源碼這里是拿到了當(dāng)前線程的Looper實(shí)例扫责,為什么一get就能有呢?因?yàn)橹骶€程創(chuàng)建的時(shí)候已經(jīng)幫我們set進(jìn)去了逃呼,所以我們不用收到set進(jìn)去鳖孤,到底是如何set進(jìn)去的?這是后就需要靈性了在當(dāng)前類搜索找到sThreadLocal.set,是的抡笼,找到了:

 /** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

 private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

最后一行看到了吧苏揣,簡單粗暴直接new了一個(gè)Looper()進(jìn)去,外層方法是prepare()推姻,方法名是準(zhǔn)備的意思平匈,看來就是調(diào)用這個(gè)準(zhǔn)備方法的時(shí)候就創(chuàng)建了一個(gè)Looper實(shí)例set進(jìn)去了,Looper的構(gòu)造函數(shù)里面直接創(chuàng)建了一個(gè)MessageQueue藏古,MessageQueue(消息隊(duì)列)是用于存放Message(消息)的增炭,此時(shí)三個(gè)基佬都有了各自的實(shí)例了,那么就可以合作干活了拧晕。
如果你對(duì)sThreadLocal感興趣弟跑,其實(shí)他就是一個(gè)static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();不懂自行學(xué)習(xí),我也不太懂先

說了這么多連Looper在哪里創(chuàng)建的都不知道防症,我TM差點(diǎn)都忘了孟辑。對(duì)于這個(gè)UI線程的Looper實(shí)例是不用我們進(jìn)行創(chuàng)建的,很多關(guān)于這部分知識(shí)的講解都有講到蔫敲,Android 在進(jìn)程的入口函數(shù) ActivityThread.main()中饲嗽,調(diào)用 Looper.prepareMainLooper, 為應(yīng)用的主線程創(chuàng)建Looper,源碼:
public final class ActivityThread {

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      if (activity != null) {
           activity.attach(appContext, this, ..., );

public static void main(String[] args) {
    // 在這兒調(diào)用 Looper.prepareMainLooper, 為應(yīng)用的主線程創(chuàng)建Looper
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
  }
}

看到注釋那里奈嘿,Looper.prepareMainLooper()這里創(chuàng)建了主線程Looper貌虾,而不是調(diào)用Looper.prepare()方法創(chuàng)建,其實(shí)Looper.prepareMainLooper()調(diào)用了Looper.prepare(false),最終還是調(diào)用了裙犹,看到這里的最后一行
Looper.loop()這個(gè)方法點(diǎn)擊進(jìn)去看是一直獲取MessageQueue的Message的尽狠,看源碼見分曉(略長衔憨,跟著注釋看):

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();    //這個(gè)方法前面講過,就是Looper 實(shí)例袄膏,如果你忘了這個(gè)方法很正常践图,看多幾次就OK
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;   //這個(gè)是消息隊(duì)列,等下就要掏空它(從里面不斷獲取Message)

    // 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();

    //這里開始進(jìn)入死循環(huán)沉馆,不斷獲取消息码党,分發(fā)消息
    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);
        }

        //這句才是關(guān)鍵,下面會(huì)對(duì)這句局進(jìn)行分析
        msg.target.dispatchMessage(msg);

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

以上邏輯相當(dāng)?shù)暮唵纬夂冢灰裮sg.target.dispatchMessage(msg)這個(gè)函數(shù)流程走完就OK了揖盘,target是啥?為什么它有dispatchMessage(msg)方法锌奴,靈性又發(fā)揮作用了兽狭,msg.target.dispatchMessage(msg)這里面的msg是從消息隊(duì)列拿到的消息,我們知道消息最后是在Handler的方法里進(jìn)行處理的鹿蜀,那target應(yīng)該就是Handler實(shí)例了箕慧,馬上去Message源碼搜索target,是的耻姥,我們發(fā)現(xiàn)他就是Handler销钝,源碼:Handler target;,這時(shí)我們需要知道什么時(shí)候?qū)⑦@個(gè)target賦值的琐簇,回到最開始我們new 出一個(gè)Handler的那里蒸健,只有在那里我們才操作了Handler,我們調(diào)用了handler.sendEmptyMessage();,看這里的源碼婉商,一直跟蹤下去:

/**
 * Sends a Message containing only the what value.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean sendEmptyMessage(int what)
{
    return sendEmptyMessageDelayed(what, 0);
}

/**
 * Sends a Message containing only the what value, to be delivered
 * after the specified amount of time elapses.
 * @see #sendMessageDelayed(android.os.Message, long) 
 * 
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

/**
 * Sends a Message containing only the what value, to be delivered 
 * at a specific time.
 * @see #sendMessageAtTime(android.os.Message, long)
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, 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) {  
   msg.target = this;  
   if (mAsynchronous) {  
       msg.setAsynchronous(true);  
   }  
   return queue.enqueueMessage(msg, uptimeMillis);  

}

跟了大半天才跟蹤到這行msg.target = this; 是的,我們?cè)谶@里將Handler放到了Message的targe字段里面了似忧,發(fā)了一條消息過來肯定要做個(gè)標(biāo)志嘛,不然這么多消息扔到消息隊(duì)列丈秩,取出來都不知道給回誰盯捌,所以我們?cè)跅l消息對(duì)象標(biāo)記好各自的Handler,最后取出來才能給到對(duì)應(yīng)的Handler蘑秽。好的饺著,知道了targe是Handler,那么msg.target.dispatchMessage(msg);的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);
    }
}

直接看else里面的,if里面的的情況需要另外分析缀雳,我們發(fā)送的這些消息會(huì)交給else處理就是了渡嚣,handleMessage(msg)等你等到想分手了,經(jīng)歷了這么多,讓我更見的了解你识椰,以后再也不會(huì)找這么久了绝葡。
記得前面是這么寫的

mHandler1.sendEmptyMessage(1);

mHandler2.sendEmptyMessage(2);

那么很自然的,下面一個(gè)輸出1腹鹉,一個(gè)輸出2

private Handler mHandler1 = new Handler() {

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        LLog.d("handleMessage", "" + msg.what);
    }
} ;

private Handler mHandler2 = new Handler() {

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        LLog.d("handleMessage", "" + msg.what);
    }
};

第一次敲博客藏畅,如果哪里講錯(cuò)或者思路有些逆天,麻煩指點(diǎn)下种蘸,如果你是剛接觸Handler墓赴,很有可能需要多看幾次才能理解竞膳。如果你完全理解了航瞭,就在子線程寫個(gè)Handler,在子線程發(fā)送信息坦辟,讓Handler處理信息吧刊侯。

4總結(jié)一下:以后堅(jiān)持寫博客,真的有用锉走。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滨彻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挪蹭,更是在濱河造成了極大的恐慌亭饵,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梁厉,死亡現(xiàn)場(chǎng)離奇詭異辜羊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)词顾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門八秃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肉盹,你說我怎么就攤上這事昔驱。” “怎么了上忍?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵骤肛,是天一觀的道長。 經(jīng)常有香客問我窍蓝,道長腋颠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任它抱,我火速辦了婚禮秕豫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己混移,他們只是感情好祠墅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歌径,像睡著了一般毁嗦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上回铛,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天狗准,我揣著相機(jī)與錄音,去河邊找鬼茵肃。 笑死腔长,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的验残。 我是一名探鬼主播捞附,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼您没!你這毒婦竟也來了鸟召?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤氨鹏,失蹤者是張志新(化名)和其女友劉穎欧募,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仆抵,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跟继,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肢础。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片还栓。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖传轰,靈堂內(nèi)的尸體忽然破棺而出剩盒,到底是詐尸還是另有隱情,我是刑警寧澤慨蛙,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布辽聊,位于F島的核電站,受9級(jí)特大地震影響期贫,放射性物質(zhì)發(fā)生泄漏跟匆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一通砍、第九天 我趴在偏房一處隱蔽的房頂上張望玛臂。 院中可真熱鬧烤蜕,春花似錦、人聲如沸迹冤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泡徙。三九已至橱鹏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堪藐,已是汗流浹背莉兰。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留礁竞,地道東北人糖荒。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像苏章,于是被迫代替她去往敵國和親寂嘉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奏瞬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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