Android 面試(五):探索 Android 的 Handler

這是 面試系列 的第五期映挂。本期我們將來探討一下 Android 異步消息處理線程 —— Handler。

往期內(nèi)容傳遞:
Android 面試(一):說說 Android 的四種啟動模式
Android 面試(二):如何理解 Activity 的生命周期
Android 面試(三):用廣播 BroadcastReceiver 更新 UI 界面真的好嗎盗尸?
Android 面試(四):Android Service 你真的能應(yīng)答自如了嗎柑船?

開始

Android 的消息機制,也就是 Handler 機制泼各,相信各位都已經(jīng)是爛熟于心了吧鞍时。即創(chuàng)建一個 Message 對象,然后借助 Handler 發(fā)送出去扣蜻,之后在 HandlerhandleMessage() 方法中獲取剛才發(fā)送的 Message 對象逆巍,然后在這里進行 UI 操作就不會出現(xiàn)崩潰了。

既然 Handler 操作都爛熟于心莽使,還講這個干什么锐极?

嗯,對芳肌,在 Android 開發(fā)中灵再,我們確實經(jīng)常用到它,對于基本代碼流程自然也是倒背如流亿笤,但了解它的原理的人卻不是很多翎迁,所以面試官通常會考驗?zāi)銓?Handler 源碼機制的理解,畢竟只有知己知彼净薛,才能百戰(zhàn)不殆嘛汪榔。

我們都知道 UI 操作只能在主線程進行,通常是怎么在子線程更新 UI 的罕拂?

  • Handler
  • Activity.runOnUiThread()
  • View.post(Runnable r)

講講 Handler 機制吧

Handler 主要由以下部分組成揍异。

  • Handler
    Handler 是一個消息輔助類全陨,主要負責(zé)向消息池發(fā)送各種消息事件Handler.sendMessage() 和處理相應(yīng)的消息事件Handler.handleMessage()

  • Message
    Message 即消息衷掷,它能容納任意數(shù)據(jù)辱姨,相當(dāng)于一個信息載體。

  • MessageQueue
    MessageQueue 如其名戚嗅,消息隊列雨涛。它按時序?qū)⑾⒉迦腙犃校钚〉臅r間戳將被優(yōu)先處理懦胞。

  • Looper
    Looper 負責(zé)從消息隊列讀取消息替久,然后分發(fā)給對應(yīng)的 Handler 進行處理。它是一個死循環(huán)躏尉,不斷地調(diào)用 MessageQueue.next() 去讀取消息蚯根,在沒有消息分發(fā)的時候會變成阻塞狀態(tài),在有消息可用時繼續(xù)輪詢胀糜。

在 Android 開發(fā)中使用 Handler 有什么需要注意的

首先自然是在工作線程中創(chuàng)建自己的消息隊列必須要調(diào)用 Looper.prepare()颅拦,并且在一個線程中只能調(diào)用一次。當(dāng)然教藻,僅僅創(chuàng)建了 Looper 還不行距帅,還必須使用 Looper.loop() 開啟消息循環(huán),要不然要 Looper 也沒用括堤。

我們平時在開發(fā)中不用調(diào)用是因為默認會調(diào)用主線程的 Looper碌秸。
此外,一個線程中只能有一個 Looper 對象和一個 MessageQueue 對象悄窃。

大概的標(biāo)準(zhǔn)寫法是這樣讥电。

Looper.prepare();
Handler mHandler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
          Log.i(TAG, "在子線程中定義Handler,并接收到消息轧抗。允趟。。");
   }
};
Looper.loop();

另外一個比較逞恢拢考察的就是 Handler 可能引起的內(nèi)存泄漏了。

Handler 可能引起的內(nèi)存泄漏

我們經(jīng)常會寫這樣的代碼涣楷。

 private final Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

當(dāng)你這樣寫的時候分唾,你一定會收到編譯器的黃色警告。

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

在 Java 中狮斗,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會隱式地持有其外部類的引用绽乔,而靜態(tài)的內(nèi)部類不會持有外部類的引用。

要解決這樣的問題碳褒,我們在繼承 Handler 的時候折砸,要么是放在單獨的類文件中看疗,要么直接使用靜態(tài)內(nèi)部類。當(dāng)需要在靜態(tài)內(nèi)部類中調(diào)用外部的 Activity 的時候睦授,我們可以直接采用弱引用進行處理两芳,所以我們大概修改后的代碼如下。

 private static final class MyHandler extends Handler{
        private final WeakReference<MainActivity> mWeakReference;
        
        public MyHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }
        
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = mWeakReference.get();
            if (activity != null){
                // 開始寫業(yè)務(wù)代碼
            }
        }
    }
    
private MyHandler mMyHandler = new MyHandler(this);

其實在我們實際開發(fā)中去枷,不止一個地方可能會用到內(nèi)部類怖辆,我們都需要在這樣的情況下盡量使用靜態(tài)內(nèi)部類加弱引用的方式解決我們可能出現(xiàn)的內(nèi)存泄漏問題。

用過 HandlerThread 嗎删顶?它是干嘛的竖螃?

HandlerThread 是 Android API 提供的一個便捷的類,使用它可以讓我們快速地創(chuàng)建一個帶有 Looper 的線程逗余,有了 Looper 這個線程特咆,我們又可以生成 Handler,本質(zhì)上也就是一個建立了內(nèi)部 Looper 的普通 Thread录粱。

我們在上面提到的子線程建立 Handler 大概代碼是這樣腻格。

new Thread(new Runnable() {
    @Override public void run() {
        // 準(zhǔn)備一個 Looper,Looper 創(chuàng)建時對應(yīng)的 MessageQueue 也會被創(chuàng)建
        Looper.prepare();
        // 創(chuàng)建 Handler 并 post 一個 Message 到 MessageQueue
        new Handler().post(new Runnable() {
            @Override 
            public void run() {
                  MLog.i("Handler in " + Thread.currentThread().getName());
            }
        });
        // Looper 開始不斷的從 MessageQueue 取出消息并再次交給 Handler 執(zhí)行
        // 此時 Lopper 進入到一個無限循環(huán)中关摇,后面的代碼都不會被執(zhí)行
        Looper.loop();
    }
}).start();

而采用 HandlerThread 可以直接把步驟簡化為這樣:

// 1. 創(chuàng)建 HandlerThread 并準(zhǔn)備 Looper
handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();

// 2. 創(chuàng)建 Handler 并綁定 handlerThread 的 Looper
new Handler(handlerThread.getLooper()).post(new Runnable() {
    @Override 
    public void run() {
          // 注意:Handler 綁定了子線程的 Looper荒叶,這個方法也會運行在子線程,不可以更新 UI
          MLog.i("Handler in " + Thread.currentThread().getName());
    }
});

// 3. 退出
@Override public void onDestroy() {
    super.onDestroy();
    handlerThread.quit();
}

其中必須注意的是输虱,workerThread.start() 是必須要執(zhí)行的些楣。

至于如何使用 HandlerThread 來執(zhí)行任務(wù),主要是調(diào)用 Handler 的 API宪睹。

  • 使用 post 方法提交任務(wù)愁茁,postAtFrontOfQueue() 將任務(wù)加入到隊列前端, postAtTime() 指定時間提交任務(wù)亭病, postDelayed() 延后提交任務(wù)鹅很。
  • 使用 sendMessage() 方法可以發(fā)送消息,sendMessageAtFrontOfQueue() 將該消息放入消息隊列前端罪帖,sendMessageAtTime() 指定時間發(fā)送消息促煮, sendMessageDelayed() 延后提交消息。

HandlerThread 的 quit() 和 quitSafety() 有啥區(qū)別整袁?

兩個方法作用都是結(jié)束 Looper 的運行菠齿。它們的區(qū)別是,quit() 方法會直接移除 MessageQueue 中的所有消息坐昙,然后終止 MesseageQueue绳匀,而 quitSafety() 會將 MessageQueue 中已有的消息處理完成后(不再接收新消息)再終止 MessageQueue

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疾棵,隨后出現(xiàn)的幾起案子戈钢,更是在濱河造成了極大的恐慌,老刑警劉巖是尔,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殉了,死亡現(xiàn)場離奇詭異,居然都是意外死亡嗜历,警方通過查閱死者的電腦和手機宣渗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梨州,“玉大人痕囱,你說我怎么就攤上這事”┙常” “怎么了鞍恢?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長每窖。 經(jīng)常有香客問我帮掉,道長,這世上最難降的妖魔是什么窒典? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任蟆炊,我火速辦了婚禮,結(jié)果婚禮上瀑志,老公的妹妹穿的比我還像新娘涩搓。我一直安慰自己,他們只是感情好劈猪,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布昧甘。 她就那樣靜靜地躺著,像睡著了一般战得。 火紅的嫁衣襯著肌膚如雪充边。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天常侦,我揣著相機與錄音浇冰,去河邊找鬼。 笑死聋亡,一個胖子當(dāng)著我的面吹牛湖饱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杀捻,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了致讥?” 一聲冷哼從身側(cè)響起仅仆,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎垢袱,沒想到半個月后墓拜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡请契,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年咳榜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爽锥。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡涌韩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氯夷,到底是詐尸還是另有隱情臣樱,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布腮考,位于F島的核電站雇毫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏踩蔚。R本人自食惡果不足惜棚放,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馅闽。 院中可真熱鬧飘蚯,春花似錦、人聲如沸捞蛋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拟杉。三九已至庄涡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搬设,已是汗流浹背穴店。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拿穴,地道東北人泣洞。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像默色,于是被迫代替她去往敵國和親球凰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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

  • 久違的晴天,家長會呕诉。 家長大會開好到教室時缘厢,離放學(xué)已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗甩挫。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,495評論 16 22
  • 今天感恩節(jié)哎贴硫,感謝一直在我身邊的親朋好友。感恩相遇伊者!感恩不離不棄英遭。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,551評論 0 11
  • 可愛進取亦渗,孤獨成精挖诸。努力飛翔,天堂翱翔央碟。戰(zhàn)爭美好税灌,孤獨進取。膽大飛翔亿虽,成就輝煌菱涤。努力進取,遙望洛勉,和諧家園粘秆。可愛游走...
    趙原野閱讀 2,716評論 1 1
  • 在妖界我有個名頭叫胡百曉收毫,無論是何事攻走,只要找到胡百曉即可有解決的辦法。因為是只狐貍大家以訛傳訛叫我“傾城百曉”此再,...
    貓九0110閱讀 3,255評論 7 3