Handler

Start

前言

轉(zhuǎn)載至:https://github.com/francistao/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

Android的線程間通信主要Handler喂链、Looper、Message晃痴、MessageQueue這四個缀辩。

1. Looper

在 Looper 中維持一個 Thread 對象以及 MessageQueue彤避,通過 Looper 的構(gòu)造函數(shù)可以知道:

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

Looper 在構(gòu)造函數(shù)里干了兩件事:

  • 將線程對象指向了創(chuàng)建 Looper 的線程
  • 創(chuàng)建了一個新的 MessageQueue

Looper 主要有兩個方法:

  • looper.loop()
  • looper.prepare()

1.1 looper.loop()

在當(dāng)前線程啟動一個Message loop機(jī)制,此段代碼將直接分析出Looper、Handler线得、Message够话、MessageQueue的關(guān)系:

public static void loop() {
        final Looper me = myLooper();//獲得當(dāng)前線程綁定的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//獲得與Looper綁定的MessageQueue

        // 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ā)對象到Handler中消費
        for (;;) {
            Message msg = queue.next(); // 不斷的取下一個Message對象女嘲,在這里可能會造成堵塞畜份。
            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是神馬欣尼?什么時候被賦值的爆雹? 
            //我們一會分析Handler的時候就會講到
            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);
            }
            
            //當(dāng)分發(fā)完Message之后,當(dāng)然要標(biāo)記將該Message標(biāo)記為 *正在使用* 啦
            msg.recycleUnchecked();
        }
    }

分析了上面的源代碼愕鼓,我們可以意識到钙态,最重要的方法是:

  • queue.next()
  • msg.target.dispatchMessage(msg)
  • msg.recycleUnchecked()

Looper 中最重要的部分都是由 Message、MessageQueue 組成的菇晃!這段最重要的代碼中涉及到了四個對象册倒,他們與彼此的關(guān)系如下:

  • MessageQueue :裝食物的容器
  • Message :被裝的食物
  • msg.target :食物消費者
  • Looper :負(fù)責(zé)分發(fā)食物的人

1.2 looper.prepare()

在當(dāng)前線程關(guān)聯(lián)一個Looper對象。

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //在當(dāng)前線程綁定一個Looper
        sThreadLocal.set(new Looper(quitAllowed));
 }

以上代碼只做了兩件事:

  1. 判斷當(dāng)前線程有沒有 Looper磺送,如果有就拋出異常(Android 規(guī)定一個線程只能擁有一個與自己關(guān)聯(lián)的 Looper)剩失。

  2. 如果沒有就設(shè)置一個新的 Looper 到當(dāng)前線程

2. Handler

我們通常使用 Handler 的通常方法是:

 Handler handler = new Handler(){
        //這個方法是在哪里被回調(diào)的?
        @Override
        public void handleMessage(Message msg) {
            //Handler your Message
        }
 };

還是先看一下 Handler 的構(gòu)造方法:

//空參數(shù)的構(gòu)造方法與之對應(yīng)册着,這里只給出主要的代碼拴孤,具體大家可以到源碼中查看
public Handler(Callback callback, boolean async) {
        //打印內(nèi)存泄露提醒log
        ....
        
        //獲取與創(chuàng)建Handler線程綁定的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //獲取與Looper綁定的MessageQueue
        //因為一個Looper就只有一個MessageQueue,也就是與當(dāng)前線程綁定的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
        
}

帶上問題:

  1. Looper.loop() 死循環(huán)中的 msg.target 是什么時候被賦值的甲捏?
  2. handler.handlerMessage(msg) 在什么時候被回調(diào)的演熟?

2.1 Looper.loop() 死循環(huán)中的msg.target是什么時候被賦值的?

要分析這個問題,很自然的我們想到從發(fā)送消息開始芒粹,無論是handler.sendMessage(msg) 還是 handler.sendEmptyMessage(what)兄纺,我們最終都可以追溯到以下方法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //引用Handler中的MessageQueue
        //這個MessageQueue就是創(chuàng)建Looper時被創(chuàng)建的MessageQueue
        MessageQueue queue = mQueue;
        
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //將新來的Message加入到MessageQueue中
        return enqueueMessage(queue, msg, uptimeMillis);
}

接下來分析 enqueueMessage(queue, msg, uptimeMillis):

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //顯而易見,在此給msg.target賦值handler化漆!
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

2.2 handler.handleMessage(msg) 在什么時候被回調(diào)的估脆?

通過以上的分析,我們很明確的知道 Message 中的 target 是在什么時候被賦值的了座云,我們先來分析在 Looper.loop() 中出現(xiàn)過的過的 dispatchMessage(msg) 方法:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //在這里回調(diào)
            handleMessage(msg);
        }
}

加上以上分析疙赠,我們將之前分析結(jié)果串起來,就可以知道了某些東西: Looper.loop() 不斷地獲取 MessageQueue 中的 Message朦拖,然后調(diào)用與 Message 綁定的 Handler對象的 dispatchMessage 方法圃阳,最后,我們看到了 handleMessage 就在 dispatchMessage 方法里被調(diào)用的璧帝。

3. Handler內(nèi)存泄漏分析及解決

首先捍岳,請瀏覽下面這段 handler 代碼:

public class SampleActivity extends Activity {
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

在使用 handler 時,這是一段很常見的代碼睬隶。但是锣夹,它卻會造成嚴(yán)重的內(nèi)存泄漏問題。在實際編寫中苏潜,我們往往會得到如下警告:

??:In Android, Handler classes should be static or leaks might occur.

3.1 分析

3.1.1 Android 角度

當(dāng) Android 應(yīng)用程序啟動時银萍,framework 會為該應(yīng)用程序的主線程創(chuàng)建一個 Looper 對象。這個 Looper 對象包含一個簡單的消息隊列Message Queue窖贤,并且能夠循環(huán)的處理隊列中的消息砖顷。這些消息包括大多數(shù)應(yīng)用程序 framework 事件贰锁,例如 Activity 生命周期方法調(diào)用赃梧、button 點擊等,這些消息都會被添加到消息隊列中并被逐個處理豌熄。

另外授嘀,主線程的 Looper 對象會伴隨該應(yīng)用程序的整個生命周期。

然后锣险,當(dāng)主線程里蹄皱,實例化一個 Handler 對象后,它就會自動與主線程 Looper 的消息隊列關(guān)聯(lián)起來芯肤。所有發(fā)送到消息隊列的消息 Message 都會擁有一個對 Handler 的引用巷折,所以當(dāng) Looper 來處理消息時,會據(jù)此回調(diào) [Handler#handleMessage(Message)] 方法來處理消息崖咨。

3.1.2 Java角度

在 java 里锻拘,非靜態(tài)內(nèi)部類匿名類 都會潛在的引用它們所屬的外部類。但是,靜態(tài)內(nèi)部類卻不會署拟。

3.2 泄漏來源

請瀏覽下面一段代碼:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

當(dāng) activity 結(jié)束 (finish) 時婉宰,里面的延時消息在得到處理前,會一直保存在主線程的消息隊列里持續(xù)10分鐘推穷。而且心包,由上文可知,這條消息持有對 handler 的引用馒铃,而 handler 又持有對其外部類(在這里蟹腾,即 SampleActivity)的潛在引用。這條引用關(guān)系會一直保持直到消息得到處理骗露,從而岭佳,這阻止了SampleActivity 被垃圾回收器回收,同時造成應(yīng)用程序的泄漏萧锉。

??:上面代碼中的Runnable類--非靜態(tài)匿名類--同樣持有對其外部類的引用珊随。從而也導(dǎo)致泄漏。

3.3 解決方案

首先柿隙,上面已經(jīng)明確了內(nèi)存泄漏來源:

只要有未處理的消息叶洞,那么消息會引用 handler,非靜態(tài)的 handler 又會引用外部類禀崖,即 Activity衩辟,導(dǎo)致 Activity 無法被回收,造成泄漏波附;

Runnable 類屬于非靜態(tài)匿名類艺晴,同樣會引用外部類。

為了解決遇到的問題掸屡,我們要明確一點:靜態(tài)內(nèi)部類不會持有對外部類的引用封寞。所以,我們可以把 handler 類放在單獨的類文件中仅财,或者使用靜態(tài)內(nèi)部類便可以避免泄漏狈究。

另外,如果想要在 handler 內(nèi)部去調(diào)用所在的外部類 Activity盏求,那么可以在 handler 內(nèi)部使用弱引用的方式指向所在 Activity抖锥,這樣統(tǒng)一不會導(dǎo)致內(nèi)存泄漏。

對于匿名類 Runnable碎罚,同樣可以將其設(shè)置為靜態(tài)類磅废。因為靜態(tài)的匿名類不會持有對外部類的引用。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

原文鏈接??

4. 總結(jié)

通過以上的分析荆烈,我們可以很清晰的知道 Handler拯勉、Looper、Message、MessageQueue 這四者的關(guān)系以及如何合作的了谜喊。

當(dāng)我們調(diào)用 handler.sendMessage(msg) 方法發(fā)送一個 Message 時潭兽,實際上這個 Message 是發(fā)送到與當(dāng)前線程綁定的一個 MessageQueue 中,然后與當(dāng)前線程綁定的 Looper 將會不斷的從 MessageQueue 中取出新的 Message斗遏,調(diào)用 msg.target.dispathMessage(msg) 方法將消息分發(fā)到與 Message 綁定的 handler.handleMessage() 方法中山卦。

一個 Thread 對應(yīng)多個 Handler, 一個 Thread 對應(yīng)一個 Looper 和 MessageQueue诵次,Handler 與 Thread 共享 Looper 和 MessageQueue账蓉。 Message 只是消息的載體,將會被發(fā)送到與線程綁定的唯一的 MessageQueue 中逾一,并且被與線程綁定的唯一的 Looper 分發(fā)铸本,被與其自身綁定的 Handler 消費。

申明:開始的圖片來源網(wǎng)絡(luò)遵堵,侵刪

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箱玷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陌宿,更是在濱河造成了極大的恐慌锡足,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳坪,死亡現(xiàn)場離奇詭異舶得,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爽蝴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門沐批,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝎亚,你說我怎么就攤上這事九孩。” “怎么了颖对?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵捻撑,是天一觀的道長磨隘。 經(jīng)常有香客問我缤底,道長,這世上最難降的妖魔是什么番捂? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任个唧,我火速辦了婚禮,結(jié)果婚禮上设预,老公的妹妹穿的比我還像新娘徙歼。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布魄梯。 她就那樣靜靜地躺著桨螺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酿秸。 梳的紋絲不亂的頭發(fā)上灭翔,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音辣苏,去河邊找鬼肝箱。 笑死,一個胖子當(dāng)著我的面吹牛稀蟋,可吹牛的內(nèi)容都是我干的煌张。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼退客,長吁一口氣:“原來是場噩夢啊……” “哼骏融!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萌狂,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绎谦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后粥脚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窃肠,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年刷允,在試婚紗的時候發(fā)現(xiàn)自己被綠了冤留。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡树灶,死狀恐怖纤怒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情天通,我是刑警寧澤泊窘,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站像寒,受9級特大地震影響烘豹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诺祸,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一携悯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筷笨,春花似錦憔鬼、人聲如沸龟劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昌跌。三九已至,卻和暖如春照雁,著一層夾襖步出監(jiān)牢的瞬間避矢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工囊榜, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留审胸,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓卸勺,卻偏偏與公主長得像砂沛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子曙求,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

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