扒一扒面試必問(wèn)的Handler

0.前言

Handler作為Android代碼編寫(xiě)以及面試時(shí)經(jīng)常遇到的內(nèi)容溉浙,有必要花個(gè)時(shí)間整理一下迹蛤,畢竟寫(xiě)過(guò)的東西印象會(huì)更加深刻民珍。

1.什么是Handler?

1.1 定義

源碼里面撈出來(lái)的內(nèi)容,英文不難看懂盗飒。主要就是說(shuō)每個(gè)Handler會(huì)和每個(gè)線(xiàn)程以及線(xiàn)程對(duì)應(yīng)的消息隊(duì)列相綁定嚷量。之后消息就可通過(guò)Handler在線(xiàn)程之間傳遞。

A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread's {@link 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.
  <p>There are two main uses for a Handler:
  (1) to schedule messages and runnables to be executed at some point in the future; and 
  (2) to enqueue an action to be performed on a different thread than your own.

1.2 Handler的作用

Handler 的作用主要為兩個(gè):

  1. 讓程序在未來(lái)的某個(gè)時(shí)間點(diǎn)去執(zhí)行一些事情
  2. 事件跨線(xiàn)程傳遞執(zhí)行(最主要的使用就是子線(xiàn)程讓主線(xiàn)程去做一些刷新的事情)

1.3 Handler的基本用法

1.在未來(lái)某個(gè)時(shí)刻去執(zhí)行runnable中的事件

handler.post(runnable,delayMillis);

2.不同消息間傳遞事件

android.os.Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {
    //這里接受并處理消息
  }
};
//發(fā)送消息
handler.sendMessage(message);

2.Handler基本原理

2.1 Handler原理相關(guān)的類(lèi)

  • Handler
  • Looper
  • MessageQueue
  • Messagre

2.2 Handler原理概述

首先放一張Handler消息處理的原理圖

根據(jù)圖一步步梳理Handler實(shí)現(xiàn)機(jī)制

2.3 Handler工作流程

2.3.1 創(chuàng)建Handler

首先第一步是創(chuàng)建Handler,Handler的構(gòu)造函數(shù)主要有如下幾個(gè)

其中的參數(shù)如下

  • boolean async

    async 該參數(shù)確定用Handler發(fā)送的消息是否要設(shè)置成異步消息蝶溶,如果為ture設(shè)置為異步消息,異步消息可和同步阻塞一起配合使用,典型的例子是界面的定時(shí)刷新抖所。

  • Callback callback

    設(shè)置了該參數(shù)梨州,那么Handler在dispatchMessage回調(diào)時(shí)會(huì)優(yōu)先調(diào)用callback中的代碼,若返回為true,則不再執(zhí)行handleMessage的回調(diào)

/**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

  • Looper looper

    設(shè)置該Hanlder對(duì)應(yīng)的Looper參數(shù)田轧,對(duì)于每個(gè)Handler,都有一個(gè)對(duì)應(yīng)的Looper,每個(gè)Looper有其對(duì)應(yīng)的MessageQueue暴匠。如果沒(méi)有顯式的設(shè)置Handler的Looper,那么Handler默認(rèn)會(huì)取該線(xiàn)程對(duì)應(yīng)的Looper賦給該Handler。

2.3.2 Handler發(fā)送消息

Handler發(fā)送消息主要調(diào)用的是sendMessage方法傻粘。

sendMessage(@NonNull Message msg)

發(fā)送消息的方法有許多不同的名稱(chēng)和參數(shù)每窖。但是,你從源碼一步步看最后都會(huì)指向如下的這段代碼

public boolean sendMessageAtTime(@NonNull 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);
    }

看這段代碼我們會(huì)發(fā)現(xiàn)弦悉,其主要的操作就是調(diào)用壓隊(duì)列的方法窒典。傳入的參數(shù)是隊(duì)列,消息以及對(duì)應(yīng)的事件

2.3.3 MessageQueue壓入消息

該方法首先是對(duì)Message 實(shí)例進(jìn)行各種參數(shù)的設(shè)置稽莉,最主要的就是target參數(shù)瀑志,該參數(shù)用于確定消息從MessageQueue中取出后去調(diào)用哪個(gè)Handler的dispatchMessage,從而進(jìn)行消息的處理污秆。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

設(shè)置完參數(shù)劈猪,Handler的enqueueMessage就會(huì)去調(diào)用MessageQueue的enqueueMessage方法,代碼如下

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

從代碼中我們能發(fā)現(xiàn)良拼,消息隊(duì)列主要是通過(guò)鏈表的形式進(jìn)行的存儲(chǔ)岸霹。這段代碼的所處理的事情就是根據(jù)Message中的when參數(shù)從列表中來(lái)找到需要插入的點(diǎn),并把Message進(jìn)行參入将饺。

2.3.4 Looper取出消息

Looper取出消息主要是調(diào)用了Looper類(lèi)的loop方法贡避,該方法里面有一個(gè)for(;;)循環(huán),不停的從MessageQueue中取出消息予弧。

 for (;;) 
    {
        Message msg = queue.next();
        ……
    }

2.3.5 Handler處理消息

當(dāng)消息取出以后刮吧,即會(huì)調(diào)用

 msg.target.dispatchMessage(msg);


來(lái)處理消息,這個(gè)target即時(shí)開(kāi)始enqueMessage時(shí)塞入的Handler掖蛤。

從Handler中我們可以看到杀捻,這dispatchMessage(Message msg)方法。

 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

其中handleMessage(msg)就是我們自定義Handler時(shí)的回調(diào)蚓庭,這樣消息就走完了整個(gè)流程致讥。

3.Handler延伸

3.1 Q Handler 為什么會(huì)導(dǎo)致內(nèi)存泄漏,該如何處理這個(gè)內(nèi)存泄漏問(wèn)題器赞?

A: Handler的普通用法如下:

  Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {

        }
    };

由于Handler是通過(guò)匿名內(nèi)部類(lèi)的方式實(shí)現(xiàn)的垢袱,所以其會(huì)對(duì)外部類(lèi)有引用(通常為Activity),這就會(huì)引起內(nèi)存泄漏港柜。

通常的解決如下

把Handler定義成靜態(tài)內(nèi)部類(lèi)请契,如Handler中需要對(duì)外部類(lèi)的參數(shù)有引用咳榜,那么可以使用弱引用,示例代碼如下

 private static class MyHandler extends Handler{
        //持有弱引用HandlerActivity,GC回收時(shí)會(huì)被回收掉.
        private final WeakReference<HandlerActivity> mActivty;
        public MyHandler(HandlerActivity activity){
            mActivty =new WeakReference<HandlerActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity=mActivty.get();
            super.handleMessage(msg);
            if(activity!=null){
                //執(zhí)行業(yè)務(wù)邏輯
            }
        }
    }

3.2 Q Handler類(lèi)中有個(gè)Callback的接口爽锥,請(qǐng)問(wèn)這個(gè)接口有什么用涌韩?

A: 這個(gè)的設(shè)計(jì)主要是為了解決Handler內(nèi)存泄漏的問(wèn)題。

可參考如下代碼:

Handler handler = new Handler(new Handler.Callback(

@Override

public boolean handleMessage(Message msg) {

if (msg.what ==1){

textView.setText("Hello!");

return true;

    }

return false;

));


該接口的調(diào)用主要出現(xiàn)在dispatchMessage中氯夷,從此處的代碼可知道臣樱,如果mCallback變量沒(méi)有定義,變量為空腮考,會(huì)走Handler系統(tǒng)的方法hadlerMessage(Message msg),而如果mCallback變量定義了擎淤,那么其會(huì)運(yùn)行到mCallback

 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

3.3 Q Handler中的普通消息,異步消息和消息屏障是什么關(guān)系秸仙?有什么作用?

MessageQueue中的Message一共有三種類(lèi)型桩盲,即普通消息寂纪,異步消息和消息屏障。區(qū)別是消息屏障它的target為null赌结。而普通消息和異步消息是通過(guò)setAsynchronous為true來(lái)進(jìn)行區(qū)分的捞蛋。

在設(shè)置消息屏障后,異步消息具有優(yōu)先處理的權(quán)利柬姚。界面的定時(shí)刷新就是用的這個(gè)機(jī)制

3.4 Q Message的target是什么時(shí)候注入的拟杉?

撈取Handler的源碼發(fā)現(xiàn)如下這段

 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }


即哪個(gè)Handler把Message存入的消息隊(duì)列,最后回調(diào)的就是這個(gè)Handler對(duì)應(yīng)的dispatchMessage 方法量承。

3.5 Q Looper會(huì)不停的從消息隊(duì)列中取消息嗎搬设?

不會(huì),若MessageQueue中消息已經(jīng)取完或者消息要在之后的某個(gè)事件才會(huì)促發(fā)撕捍,則會(huì)調(diào)用native方法 nativePollOnce(long ptr, int timeoutMillis)拿穴,使主線(xiàn)程處于等待狀態(tài)。當(dāng)調(diào)用了往消息隊(duì)列塞入消息時(shí)忧风,則會(huì)調(diào)用nativeWake(long ptr)方法喚醒主線(xiàn)程默色,所以盡管,loop方法中有個(gè)for死循環(huán)狮腿,但是這不會(huì)導(dǎo)致Looper不停從消息隊(duì)列取數(shù)據(jù)腿宰。

3.6 Q Handler的post(Runnable r) 方法是如何實(shí)現(xiàn)Runnable中事件的執(zhí)行的?

post方法從本質(zhì)上來(lái)說(shuō)也是用的Message消息傳遞的流程缘厢。

 public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

從如下兩端代碼可看出其是對(duì)runnable進(jìn)行了封裝吃度,把runnable塞進(jìn)了Message中。而當(dāng)Handler獲得消息后贴硫,其會(huì)取出msg.callback參數(shù)规肴,然后運(yùn)行里面的runnable方法。

3.7 Q 平時(shí)代碼編寫(xiě)中如何獲得Message對(duì)象比較好?

用Message.obtain()獲取比較好拖刃,避免了gc導(dǎo)致的性能消耗删壮。

3.8 Q IdleHandler是什么,有什么作用兑牡?

IdleHandler是在主線(xiàn)程消息隊(duì)列空閑時(shí)央碟,會(huì)被取出執(zhí)行的對(duì)象【可在一些頁(yè)面優(yōu)化的情景下使用亿虽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市苞也,隨后出現(xiàn)的幾起案子洛勉,更是在濱河造成了極大的恐慌,老刑警劉巖如迟,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件收毫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡殷勘,警方通過(guò)查閱死者的電腦和手機(jī)此再,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)玲销,“玉大人输拇,你說(shuō)我怎么就攤上這事∠托保” “怎么了策吠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瘩绒。 經(jīng)常有香客問(wèn)我奴曙,道長(zhǎng),這世上最難降的妖魔是什么草讶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任洽糟,我火速辦了婚禮,結(jié)果婚禮上堕战,老公的妹妹穿的比我還像新娘坤溃。我一直安慰自己,他們只是感情好嘱丢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布薪介。 她就那樣靜靜地躺著,像睡著了一般越驻。 火紅的嫁衣襯著肌膚如雪汁政。 梳的紋絲不亂的頭發(fā)上道偷,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音记劈,去河邊找鬼勺鸦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛目木,可吹牛的內(nèi)容都是我干的换途。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刽射,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼军拟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起誓禁,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤懈息,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后摹恰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辫继,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年戒祠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片速种。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姜盈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出配阵,到底是詐尸還是另有隱情馏颂,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布棋傍,位于F島的核電站救拉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘫拣。R本人自食惡果不足惜亿絮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望麸拄。 院中可真熱鬧派昧,春花似錦、人聲如沸拢切。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)淮椰。三九已至五慈,卻和暖如春纳寂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泻拦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工毙芜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聪轿。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓爷肝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親陆错。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灯抛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345