Android筆記之Handler異步消息處理機(jī)制

主要參考文獻(xiàn):深入理解Android,卷1
       Android 5.0 源碼

學(xué)識(shí)尚淺,錯(cuò)誤之處請(qǐng)指正俄认。

為什么需要這種機(jī)制?

<p>在Android的應(yīng)用開(kāi)發(fā)中我們經(jīng)常遇到這樣的場(chǎng)景碍脏,為了避免在UI線程中的耗時(shí)操作產(chǎn)生ANR,我們會(huì)開(kāi)啟新的線程來(lái)處理好事操作梭依,得到處理結(jié)果在對(duì)UI進(jìn)行更新。
但是因?yàn)閍ndroid本身設(shè)計(jì)時(shí)考慮到性能優(yōu)先典尾,所以UI操作并非線程安全的,為了避免多線程操作UI帶來(lái)的線程安全問(wèn)題糊探,android設(shè)計(jì)者直接規(guī)定:只允許UI線程來(lái)操作UI組件钾埂。
為了解決這個(gè)問(wèn)題我們需要考慮這么幾個(gè)問(wèn)題:

  1. 解決線程間的相互通信。
  2. 一方發(fā)送的消息另一方能夠及時(shí)收到科平,并做出處理褥紫。

Handler異步消息處理機(jī)制就是Android中解決這個(gè)問(wèn)題提供的一種方案。

基本的原理很簡(jiǎn)單:

<p>

  • 一個(gè)消息隊(duì)列:可以往其中添加消息瞪慧。
  • 一個(gè)消息循環(huán):持續(xù)不斷的從消息隊(duì)列中取出消息髓考,進(jìn)行處理。
    <p>

他的四個(gè)組成部分Message弃酌、Handler氨菇、MessageQueue和Looper儡炼,形象的說(shuō)明了各自的分工:

  • Message:封裝需要傳遞的消息
  • Handler:發(fā)送和處理消息
  • MessageQueue:消息隊(duì)列,采用先進(jìn)先出的方式管理Message
  • Looper:他負(fù)責(zé)管理MessageQueue查蓉,不斷的從中讀取消息乌询,并將讀到的消息交給Handler處理。
圖片來(lái)源:http://www.lxway.com/608450004.htm

舉一個(gè)并不確切的例子說(shuō)一下我的理解:

Handler好比一個(gè)要搬家的人豌研,不同的線程就像是舊的住所和新的住所妹田,你在舊的住所將自己的東西打包好裝在行李箱(Message )中,并在上面貼上自己的標(biāo)簽,如姓名聯(lián)系方式等(Message.target);MessageQueque好比是物流鹃共,你將打包好的行李箱交給他鬼佣,他幫你運(yùn)輸和存儲(chǔ);looper就好比送貨員,他將運(yùn)送過(guò)來(lái)的消息取出來(lái)霜浴,查看主人(target),它將你的東西送到你的手里晶衷,任憑你的處理和擺放。

如何簡(jiǎn)單地使用它

<p>網(wǎng)上有很多這樣的教程坷随,但是我看了一些房铭,覺(jué)得大多數(shù)單單使用這一點(diǎn)并沒(méi)有講清楚,讓初次接觸的人用起來(lái)非常復(fù)雜温眉,其實(shí)事實(shí)并不是這樣的缸匪。希望我能夠嘗試這說(shuō)清楚。
首先它是一種線程間通信的通信的方式类溢,因此至少涉及到了兩個(gè)線程凌蔬,我們將這兩個(gè)線程按照一次消息處理的地位分為發(fā)送消息線程和接受消息線程。

對(duì)于接受線程:

  1. 創(chuàng)建Looper對(duì)象闯冷。
    Looper.prepare();使用Looper的prepare()方法即可創(chuàng)建一個(gè)Looper對(duì)象砂心,其中也包含了MessageQueque對(duì)象的創(chuàng)建,當(dāng)然每一個(gè)線程中只能允許擁有一個(gè)Looper對(duì)象蛇耀。
  2. 初始化Handler對(duì)象辩诞,并重寫(xiě)其handleMessage()方法來(lái)處理接受的消息。
  3. 啟動(dòng)Looper纺涤。
    Looper.loop();使用Looper的loop()方法即可啟動(dòng)loop,這樣Looper對(duì)象才能不斷的管理MessageQueque译暂。

對(duì)于發(fā)送線程:

  1. 創(chuàng)建攜帶信息的Message對(duì)象。
  2. 使用接受線程創(chuàng)建的Handler對(duì)象來(lái)發(fā)送消息撩炊。
    reThread.mHandler.sendMessage(msg);
簡(jiǎn)單的小程序
/**
 * 該例子是在子線程為發(fā)送消息的線程外永,主線程中接收消息
 */
public class HandlerTestActivity extends AppCompatActivity {

    //聲明Handler變量
    private Handler mHandler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);

        //開(kāi)啟一個(gè)接受線程
        Thread reThread = new Thread(){
          @Override
          public void run(){
              //1.使用prepare函數(shù)創(chuàng)建Looper對(duì)象
              Looper.prepare();
              //2.初始化Handler對(duì)象
              mHandler = new Handler(){
                  //3.重寫(xiě)handleMessage方法來(lái)處理接收到的消息
                  @Override
                  public void handleMessage(Message msg) {
                      //Message.what字段可以攜帶少量信息,這里作為身份識(shí)別信息
                      if(msg.what==0x123){
                          //接收到消息拧咳,彈出對(duì)話框提醒
                          Toast.makeText(HandlerTestActivity.this,"reThread接收到消息",Toast.LENGTH_LONG).show();
                      }
                  }
              };
              //3.使用loop函數(shù)開(kāi)啟Looper
              Looper.loop();
          }
        };
        //開(kāi)啟該接收消息的線程
        reThread.start();

    }

    /**
     * 添加一個(gè)按鈕伯顶,為按鈕添加點(diǎn)擊事件
     * 在該回調(diào)函數(shù)中向接收線程發(fā)送消息
     */
    public void sendMessageFromSendThread(View view){
        //1.創(chuàng)建Message對(duì)象
        Message sendMsg = new Message();
        //在Message的what字段中攜帶少量信息,用于消息的身份識(shí)別
        sendMsg.what = 0x123;
        //使用接收線程創(chuàng)建的Handler對(duì)象的sendMessage函數(shù)向接收線程發(fā)送消息
        mHandler.sendMessage(sendMsg);
    }
}
   

這里有一個(gè)需要注意的問(wèn)題:
我們經(jīng)常使用的場(chǎng)景中是主線程即UI線程最為接收消息的線程,然后更新UI來(lái)相應(yīng)數(shù)據(jù)的變化祭衩,在這種情況下灶体,Looper對(duì)象的創(chuàng)建和啟動(dòng)這兩個(gè)步驟可以省略,因?yàn)閍ndroid本身已經(jīng)默認(rèn)為主線程完成了Looper相關(guān)的工作汪厨,用戶只要實(shí)現(xiàn)Handler相關(guān)的即可赃春。具體我們后面再做分析。

源碼分析

<p>

1.Looper類

主要變量

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 private static Looper sMainLooper;  // guarded by Looper.class

 final MessageQueue mQueue;
 final Thread mThread;

Looper構(gòu)造器聲明為private劫乱,不允許用戶自己初始化Looper對(duì)象;自身攜帶了消息隊(duì)列MessageQueue织中。

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

用戶需要調(diào)用prepare函數(shù)來(lái)創(chuàng)建對(duì)象,可以看出prepare函數(shù)保證了一個(gè)線程中只有一個(gè)looper對(duì)象也就意味著只有一個(gè)MessageQueue消息隊(duì)列衷戈。除此之外prepare函數(shù)還利用線程的ThreadLocal變量來(lái)存儲(chǔ)該線程創(chuàng)建的Looper對(duì)象狭吼,使用這種機(jī)制來(lái)關(guān)聯(lián)looper對(duì)象和他的調(diào)用線程。

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //ThreadLocal為線程局部變量類
       //set方法設(shè)置線程的局部變量
        //get方法得到線程的局部變量
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

loop函數(shù)開(kāi)啟了一個(gè)死循環(huán)殖妇,不斷的從消息隊(duì)列中讀取消息刁笙,并將消息交給Handler對(duì)象去處理。

  /**
     * 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();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
       //略去一部分
    
        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);
            }
       //分發(fā)給message的target字段包含的目標(biāo)handler中谦趣。
            msg.target.dispatchMessage(msg);

       //略去一部分
        }
    }

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

總結(jié)Looper的作用:

  • 封裝了一個(gè)消息隊(duì)列
  • 將Looper對(duì)象和調(diào)用prepare函數(shù)的線程綁定
  • 保證了每一個(gè)線程只能有一個(gè)looper對(duì)象和消息隊(duì)列
  • 及時(shí)取出消息隊(duì)列中的消息將他交給綁定的線程中的handler進(jìn)行處理疲吸。
2.Handler類

看源碼中說(shuō)明:

A Handler allows you to send and process Message and Runnable
objects associated with a thread's MessageQueue. Each Handler
instance is associated with a single thread and that thread's message
queue. When you create a new Handler, it is bound to the thread /
message queue of the thread that is creating it -- from that point on,
it will deliver messages and runnables to that message queue and execute
them as they come out of the message queue.

There are two main uses for a Handler: (1) to schedule messages and
runnables to be executed as some point in the future; and (2) to enqueue
an action to be performed on a different thread than your own.

Handler構(gòu)造器有很多個(gè),最終會(huì)調(diào)用到這兩個(gè)前鹅。

   //沒(méi)有傳入looper對(duì)象時(shí)摘悴,使用Looper.myLooper()獲得,前面提到過(guò)myLooper函數(shù)是從當(dāng)前線程中取出綁定的looper對(duì)象舰绘。
   //然后獲得該looper對(duì)象中的消息隊(duì)列
   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();
        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;
    }
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

sendMessage相關(guān)的幾個(gè)函數(shù)蹂喻,最終會(huì)調(diào)用sendMessageAtTime這個(gè)函數(shù)。他把得到的消息Message對(duì)象和消息隊(duì)列MessageQueue交給enqueueMessage函數(shù)處理捂寿。
enqueueMessage負(fù)責(zé)在消息Message的target字段標(biāo)記為自己口四,以便接收到的消息能夠分發(fā)到自己。
接下來(lái)交給MessageQueque的enqueueMessage()方法將消息添加到消息隊(duì)列中秦陋。

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

前面Looper對(duì)象取出消息隊(duì)列中的消息后會(huì)根據(jù)msg.target調(diào)用該Handler的dispatchMessage函數(shù)蔓彩。

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        //如果msg本身包含callback,交給消息的callback去處理
        if (msg.callback != null) {
            handleCallback(msg);
        } else {//如果沒(méi)有驳概,講給Handler的callback去處理粪小,也就是重寫(xiě)的handleMessage函數(shù)去處理。
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

總結(jié)Handler的作用:

  • 封裝了將Message添加到MessageQueque的過(guò)程抡句,即發(fā)送消息過(guò)程
  • 在發(fā)送的消息中打上自己的標(biāo)簽
  • 處理自己的發(fā)送的消息

MessageQueue和Message這兩個(gè)組成部分比較簡(jiǎn)單,在此不做詳解杠愧〈疲看完了以上的代碼部分的分析,知道這個(gè)機(jī)制原理很簡(jiǎn)單,這樣再回去看看我那個(gè)不準(zhǔn)確的小例子锐锣,你會(huì)發(fā)現(xiàn)很多的不合理的之處腌闯,但是用來(lái)初步理解還是可以的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雕憔,一起剝皮案震驚了整個(gè)濱河市姿骏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斤彼,老刑警劉巖分瘦,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異琉苇,居然都是意外死亡嘲玫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)并扇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)去团,“玉大人,你說(shuō)我怎么就攤上這事穷蛹⊥僚悖” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵肴熏,是天一觀的道長(zhǎng)鬼雀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)扮超,這世上最難降的妖魔是什么取刃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮出刷,結(jié)果婚禮上璧疗,老公的妹妹穿的比我還像新娘。我一直安慰自己馁龟,他們只是感情好崩侠,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著坷檩,像睡著了一般却音。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矢炼,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天系瓢,我揣著相機(jī)與錄音,去河邊找鬼句灌。 笑死夷陋,一個(gè)胖子當(dāng)著我的面吹牛欠拾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骗绕,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼藐窄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了酬土?” 一聲冷哼從身側(cè)響起荆忍,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撤缴,沒(méi)想到半個(gè)月后刹枉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腹泌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年嘶卧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凉袱。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芥吟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出专甩,到底是詐尸還是另有隱情钟鸵,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布涤躲,位于F島的核電站棺耍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏种樱。R本人自食惡果不足惜蒙袍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫩挤。 院中可真熱鬧害幅,春花似錦、人聲如沸岂昭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)约啊。三九已至邑遏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恰矩,已是汗流浹背记盒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留外傅,地道東北人孽鸡。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓蹂午,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親彬碱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

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