Android--異步消息處理機(jī)制(Handler、Looper仇穗、Message流部、MessageQueue)

美女圖集03
Handler的由來

當(dāng)程序第一次啟動(dòng)的時(shí)候,Android會(huì)同時(shí)啟動(dòng)一條主線程(Main Thread)來負(fù)責(zé)處理與UI相關(guān)的事件纹坐,我們叫做UI線程枝冀。
Android的UI操作并不是線程安全的(出于性能優(yōu)化考慮),意味著如果多個(gè)線程并發(fā)操作UI線程,可能導(dǎo)致線程安全問題宾茂。
為了解決Android應(yīng)用多線程問題——Android平臺(tái)只允許U線程修改Activity里的UI組件瓷马,就會(huì)導(dǎo)致新啟動(dòng)的線程無法改變界面組件的屬性值。
簡單總結(jié):當(dāng)主線程隊(duì)列處理一個(gè)消息超過5秒跨晴,android就會(huì)拋出一個(gè)ANR(無響應(yīng))的異常欧聘,所以,我們需要把一些要處理時(shí)間比較長的消息端盆,放在一個(gè)單獨(dú)線程里面進(jìn)行處理怀骤,把處理以后的結(jié)果,返回給主線程運(yùn)行焕妙,就需要用到Handler來進(jìn)行線程組件的通信蒋伦。

Handler的作用
  • 讓線程延時(shí)執(zhí)行,主要用到兩個(gè)方法:
方法1:final boolean postAtTime(Runnable r, long uptimeMillis)
方法2:final boolean postDelayed(Runnable r, long delayMillis)```
* 讓任務(wù)在其他線程中執(zhí)行并返回結(jié)果焚鹊,分為兩個(gè)步驟:
**1.在新啟動(dòng)的線程中發(fā)送消息**
使用Handler對(duì)象的```sendMessage()```方法或者```sendEmptyMessage()```方法發(fā)送消息痕届。
**2.在主線程中獲取處理消息**
重寫Handler類中處理消息的方法(void handleMessage(Message msg)),當(dāng)新啟動(dòng)的線程發(fā)送消息時(shí),消息發(fā)送到與之關(guān)聯(lián)的MessageQueue末患。而Handler不斷地從MessageQueue中獲取并處理消息研叫。
#####Handler更新UI線程一般使用
(1):首先要進(jìn)行Handler聲明,復(fù)用handleMessage方法(放在主線程中)

private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO 接收消息并且去更新UI線程上的控件內(nèi)容
if (msg.what == UPDATE) {
// 更新界面上的textview
tv.setText(String.valueOf(msg.obj));
}
super.handleMessage(msg);
}
};```
(2):子線程發(fā)送Message給UI線程表示自己任務(wù)已經(jīng)執(zhí)行完成璧针,主線程可以做相應(yīng)的操作了嚷炉。

new Thread() {
       @Override
       public void run() {
          // TODO 子線程中通過handler發(fā)送消息給handler接收
                  //,由handler去更新TextView的值
          try {
              //do something
            
              Message msg = new Message();
              msg.what = UPDATE;                    
              msg.obj = "更新后的值" ;
              handler.sendMessage(msg);
          } catch (InterruptedException e) {
           e.printStackTrace();
          }
       }
 }.start();```

#####Handler原理分析
* **Handler的構(gòu)造函數(shù)**

1: public Handler()
2: public Handler(Callback callback)
3: public Handler(Looper looper)
4: public Handler(Looper looper, Callback callback) ```
第①個(gè)和第②個(gè)構(gòu)造函數(shù)都沒有傳遞Looper探橱,這兩個(gè)構(gòu)造函數(shù)都將通過調(diào)用Looper.myLooper()獲取當(dāng)前線程綁定的Looper對(duì)象申屹,然后將該Looper對(duì)象保存到名為mLooper的成員字段中。     
下面來看①②個(gè)函數(shù)源碼:

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback) {
        this(callback, false);
    }

    //他們會(huì)調(diào)用Handler的內(nèi)部構(gòu)造方法
    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());
             }
        }

        //重點(diǎn):Looper.myLooper()獲取了當(dāng)前線程保存的Looper實(shí)例
        mLooper = Looper.myLooper();

        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not 
                                       called Looper.prepare()");
        }
        //重點(diǎn):獲取MessageQueue(消息隊(duì)列)
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }```
**通過Looper.myLooper()獲取了當(dāng)前線程保存的Looper實(shí)例隧膏,又通過這個(gè)Looper實(shí)例獲取了其中保存的MessageQueue(消息隊(duì)列)哗讥。每個(gè)Handler對(duì)應(yīng)一個(gè)Looper對(duì)象,產(chǎn)生一個(gè)MessageQueue**
第③個(gè)和第④個(gè)構(gòu)造函數(shù)傳遞了Looper對(duì)象胞枕,這兩個(gè)構(gòu)造函數(shù)會(huì)將該Looper保存到名為mLooper的成員字段中杆煞。   
下面來看③④個(gè)函數(shù)源碼:
public Handler(Looper looper) {
    this(looper, null, false);
} 

public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

//他們會(huì)調(diào)用Handler的內(nèi)部構(gòu)造方法
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
第②個(gè)和第④個(gè)構(gòu)造函數(shù)還傳遞了Callback對(duì)象,Callback是Handler中的內(nèi)部接口曲稼,需要實(shí)現(xiàn)其內(nèi)部的handleMessage方法索绪,Callback代碼如下:

public interface Callback {
public boolean More ...handleMessage(Message msg);
}```
Handler.Callback是用來處理Message的一種手段,如果沒有傳遞該參數(shù)贫悄,那么就應(yīng)該重寫Handler的handleMessage方法瑞驱,也就是說為了使得Handler能夠處理Message,我們有兩種辦法:
方法1:向Hanlder的構(gòu)造函數(shù)傳入一個(gè)Handler.Callback對(duì)象窄坦,并實(shí)現(xiàn)Handler.Callback的handleMessage方法
方法2:無需向Hanlder的構(gòu)造函數(shù)傳入Handler.Callback對(duì)象唤反,但是需要重寫Handler本身的handleMessage方法        
也就是說無論哪種方式凳寺,我們都得通過某種方式實(shí)現(xiàn)handleMessage方法,這點(diǎn)與Java中對(duì)Thread的設(shè)計(jì)有異曲同工之處彤侍。

  • Handle發(fā)送消息的幾個(gè)方法源碼
//發(fā)送消息
public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
 }```

//發(fā)送空消息肠缨,并且?guī)в袝r(shí)間延遲
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}```

//發(fā)送消息,并且?guī)в袝r(shí)間延遲
 public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, 
                SystemClock.uptimeMillis() + delayMillis);
}```

//發(fā)送消息盏阶,并且?guī)в芯唧w的時(shí)間限定
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);
}```
我們可以看出他們最后都調(diào)用了sendMessageAtTime()晒奕,然后返回了enqueueMessage方法,下面看一下此方法源碼:

    private boolean enqueueMessage(MessageQueue queue
                                    , Message msg, long uptimeMillis) {
     //把當(dāng)前的handler作為msg的target屬性
          msg.target = this;
         if (mAsynchronous) {
              msg.setAsynchronous(true);
         }
         return queue.enqueueMessage(msg, uptimeMillis);
    }```
在該方法中有兩件事需要注意:
**1. msg.target = this;**該代碼將Message的target綁定為當(dāng)前的Handler
**2. queue.enqueueMessage **
變量queue表示的是Handler所綁定的消息隊(duì)列MessageQueue名斟,通過queue.enqueueMessage(msg,uptimeMillis)我們將Message放入到消息隊(duì)列中脑慧。
下面通過一張圖,來看完整的方法調(diào)用順序:
![Handler砰盐、Looper闷袒、Message、MessageQueue調(diào)用順序](http://upload-images.jianshu.io/upload_images/3416944-d9d3d3505b4ba800.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#####Looper原理分析
我們一般在主線程聲明Handler岩梳,有時(shí)我們需要繼承Thread類實(shí)現(xiàn)自己的線程功能囊骤,當(dāng)我們?cè)诶锩媛暶鱄andler的時(shí)候會(huì)報(bào)錯(cuò)。其原因是主線程中已經(jīng)實(shí)現(xiàn)了兩個(gè)重要的Looper方法冀值,下面看一看ActivityThread.java中main方法的源碼:

public static void main(String[] args) {
//......省略
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop
unexpectedly exited");
}```

  • 首先看prepare()方法
     public static void prepare() {
         prepare(true);
     }

     private static void prepare(boolean quitAllowed) {
     //證明了一個(gè)線程中只有一個(gè)Looper實(shí)例
         if (sThreadLocal.get() != null) {
             throw new RuntimeException("Only one Looper may be 
                                             created per thread");
         }
         sThreadLocal.set(new Looper(quitAllowed));
     }```
該方法會(huì)調(diào)用Looper構(gòu)造函數(shù)同時(shí)實(shí)例化出MessageQueue和當(dāng)前thread.
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
} 

public static MessageQueue myQueue() {
    return myLooper().mQueue;
}```

prepare()方法中通過ThreadLocal對(duì)象實(shí)現(xiàn)Looper實(shí)例與線程的綁定也物。(不清楚的可以查看 ThreadLocal的使用規(guī)則和源碼分析

  • loop()方法
    public static void 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;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }

            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " 
                                        + msg.callback + ": " + msg.what);
            }
            //重點(diǎn)****
            msg.target.dispatchMessage(msg);

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

            // 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();
        }
    }```
首先looper對(duì)象不能為空,就是說loop()方法調(diào)用必須在prepare()方法的后面池摧。
Looper一直在不斷的從消息隊(duì)列中通過MessageQueue的next方法獲取Message焦除,然后通過代碼```msg.target.dispatchMessage(msg)```讓該msg所綁定的Handler執(zhí)行dispatchMessage()方法以實(shí)現(xiàn)對(duì)Message的處理激况。
Handler的dispatchMessage的源碼如下:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}```
我們可以看到Handler提供了三種途徑處理Message作彤,erqie處理有前后優(yōu)先級(jí)之分:首先嘗試讓postXXX()中傳遞的Runable執(zhí)行,其次嘗試讓Handler構(gòu)造函數(shù)中傳入的Callback的handleMessage()方法處理乌逐,最后才是讓Handler自身的handleMessage()方法處理Message竭讳。

如何在子線程中使用Handler

Handler本質(zhì)是從當(dāng)前的線程中獲取到Looper來監(jiān)聽和操作MessageQueue,當(dāng)其他線程執(zhí)行完成后回調(diào)當(dāng)前線程浙踢。
子線程需要先prepare()才能獲取到Looper對(duì)象绢慢,是因?yàn)樽泳€程只是一個(gè)普通的線程,其ThreadLoacl中沒有設(shè)置過Looper洛波,所以會(huì)拋出異常胰舆,而在Looper的prepare()方法中sThreadLocal.set(new Looper())是設(shè)置了Looper的。

  • 實(shí)例代碼:定義一個(gè)類實(shí)現(xiàn)Runnable接口或繼承Thread類(一般不繼承)蹬挤。
class Rub implements Runnable {  
        public Handler myHandler;  
        // 實(shí)現(xiàn)Runnable接口的線程體 
        @Override  
        public void run() {  
             /*1缚窿、調(diào)用Looper的prepare()方法為當(dāng)前線程創(chuàng)建Looper對(duì)象并,
            創(chuàng)建Looper對(duì)象時(shí)焰扳,它的構(gòu)造器會(huì)自動(dòng)的創(chuàng)建相對(duì)應(yīng)的MessageQueue*/
             Looper.prepare();  
            
             /*2倦零、創(chuàng)建Handler子類的實(shí)例误续,重寫HandleMessage()方法
                             ,該方法處理除當(dāng)前線程以外線程的消息*/
             myHandler = new Handler() {  
                @Override  
                public void handleMessage(Message msg) {  
                    String ms = "";  
                    if (msg.what == 0x777) {  
                     
                    }  
                }  
            };  
            //3扫茅、調(diào)用Looper的loop()方法來啟動(dòng)Looper讓消息隊(duì)列轉(zhuǎn)動(dòng)起來
            Looper.loop();  
        }
    }```
**注意分成三步:**
1:調(diào)用Looper的prepare()方法為當(dāng)前線程創(chuàng)建Looper對(duì)象蹋嵌,創(chuàng)建Looper對(duì)象時(shí),它的構(gòu)造器會(huì)創(chuàng)建與之配套的MessageQueue葫隙。
2:有了Looper之后栽烂,創(chuàng)建Handler子類實(shí)例,重寫HanderMessage()方法恋脚,該方法負(fù)責(zé)處理來自于其他線程的消息愕鼓。
3:調(diào)用Looper的looper()方法啟動(dòng)Looper。然后使用這個(gè)handler實(shí)例在任何其他線程中發(fā)送消息慧起,最終處理消息的代碼都會(huì)在你創(chuàng)建的Handler實(shí)例的線程中運(yùn)行菇晃。

#####總結(jié)
* **Handler**
發(fā)送消息,它能把消息發(fā)送給Looper管理的MessageQueue蚓挤,Looper分發(fā)給它消息磺送。
* **Message**
Handler接收和處理的消息對(duì)象。
* **Looper**
每個(gè)線程只有一個(gè)Looper灿意,它負(fù)責(zé)管理對(duì)應(yīng)的MessageQueue估灿,會(huì)不斷地從MessageQueue中取出消息,并將消息Messsge分發(fā)給對(duì)應(yīng)的Handler進(jìn)行處理缤剧。
主線程中馅袁,系統(tǒng)已經(jīng)初始化了一個(gè)Looper對(duì)象,因此可以直接創(chuàng)建Handler即可荒辕,就可以通過Handler來發(fā)送消息汗销、處理消息。程序自己啟動(dòng)的子線程抵窒,程序必須自己創(chuàng)建一個(gè)Looper對(duì)象弛针,bignqie啟動(dòng)它,調(diào)用Looper.prepare()方法李皇。
* **prapare()方法**
保證每個(gè)線程最多只有一個(gè)Looper對(duì)象削茁。
* **looper()方法**
啟動(dòng)Looper,使用一個(gè)死循環(huán)不斷取出MessageQueue中的消息掉房,并將取出的消息分發(fā)給對(duì)應(yīng)的Handler進(jìn)行處理茧跋。
* **MessageQueue**
由Looper負(fù)責(zé)管理,它采用先進(jìn)先出的方式來管理Message卓囚。
* **Handler的構(gòu)造方法**
會(huì)首先得到當(dāng)前線程中保存的Looper實(shí)例瘾杭,進(jìn)而與Looper實(shí)例中的MessageQueue相關(guān)聯(lián)。
* **Handler的sendMessage方法**
會(huì)給msg的target賦值為handler自身捍岳,然后加入MessageQueue中富寿。

參考鏈接:
[線程通信基礎(chǔ)流程分析(Handler睬隶、Looper、Message页徐、MessageQueue)](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90.md)


**結(jié)束語:越努力苏潜,越幸運(yùn)!加油   ---王令
QQ:2585085940
郵箱:wang91ling@163.com
歡迎大家光臨寒舍变勇。**
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恤左,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搀绣,更是在濱河造成了極大的恐慌飞袋,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件链患,死亡現(xiàn)場離奇詭異巧鸭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)麻捻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門纲仍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贸毕,你說我怎么就攤上這事郑叠。” “怎么了明棍?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵乡革,是天一觀的道長。 經(jīng)常有香客問我摊腋,道長沸版,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任歌豺,我火速辦了婚禮推穷,結(jié)果婚禮上心包,老公的妹妹穿的比我還像新娘类咧。我一直安慰自己,他們只是感情好蟹腾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布痕惋。 她就那樣靜靜地躺著,像睡著了一般娃殖。 火紅的嫁衣襯著肌膚如雪值戳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天炉爆,我揣著相機(jī)與錄音堕虹,去河邊找鬼卧晓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛赴捞,可吹牛的內(nèi)容都是我干的逼裆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼赦政,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼胜宇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恢着,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤桐愉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后掰派,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體从诲,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年靡羡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盏求。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亿眠,死狀恐怖碎罚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纳像,我是刑警寧澤荆烈,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站竟趾,受9級(jí)特大地震影響憔购,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜岔帽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一玫鸟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧犀勒,春花似錦屎飘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至褂萧,卻和暖如春押桃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背导犹。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工唱凯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羡忘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓磕昼,卻偏偏與公主長得像壳坪,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掰烟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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