Handler原理

Handler簡(jiǎn)單使用

1.使用靜態(tài)內(nèi)部類的方式繼承Handler并重寫接受的方法handleMessage。之所以使用靜態(tài)內(nèi)部類,是因?yàn)殪o態(tài)內(nèi)部類不會(huì)持有外部類的引用
static class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 0x11:
                    String str = (String) msg.obj;
                    Log.d(TAG,"msg content == "+str);
                    break;
                default:break;
            }
        }
    }
2.獲取Handler實(shí)例
private  Handler mHander = new MyHandler();
3.獲取Message對(duì)象
1)Message msg = new Message();
2)Message msg = Message.obtain();/mHander.obtainMessage();等
obtain.what = 0x11;
obtain.obj = "Message Content";

獲取Message方式有2中科盛,建議使用第二種方式寺董,因?yàn)榈诙N方法采用了緩存池機(jī)制,避免重復(fù)創(chuàng)建新的對(duì)象。

4.發(fā)送消息
mHander.sendMessage(msg);立即發(fā)出
mHander.sendMessageDelayed(obtain,time);延時(shí)發(fā)送
5.最后在handleMessage方法中處理消息

子線程創(chuàng)建Handler

在子線程中直接創(chuàng)建Handler會(huì)報(bào)錯(cuò)
錯(cuò)誤日志為:

Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()"

代碼中可以看到 Handler在創(chuàng)建時(shí)會(huì)判斷當(dāng)前線程的Looper是否為空

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
         "Can't create handler inside thread " + Thread.currentThread()
               + " that has not called Looper.prepare()");
}

為空的話則會(huì)報(bào)錯(cuò)。myLooper方法是獲取當(dāng)前線程的Looper,Looper存儲(chǔ)早sThreadLocal

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

sThreadLocal是ThreadLocal<Looper>類型。ThreadLocal中的對(duì)象set/get方法獲取到的數(shù)據(jù)都是當(dāng)前線程的變量痰娱。
當(dāng)myLooper為空時(shí)則說(shuō)明當(dāng)前線程Looper未初始化弃榨,而初始化的方法則是:

private static void prepare(boolean quitAllowed) {
      if (sThreadLocal.get() != null) {
          throw new RuntimeException("Only one Looper may be created per thread");
      }
      sThreadLocal.set(new Looper(quitAllowed));
}

所以子線程中如果創(chuàng)建Handler則需要調(diào)用Looper.prepare()方法。而Looper.loop方法則是核心梨睁,消息的傳遞全依賴于此鲸睛。

Handler原理

Handler的運(yùn)行機(jī)制依賴于MessageQueue與Looper的支持。
我們從代碼角度來(lái)看一下這三者之間的關(guān)系:
從入口Handler.sendMessage方法看起:

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
}
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);
}
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);
}

sendMessage方法鏈看下來(lái)坡贺,最終調(diào)用了mQueueenqueueMessage方法官辈,mQueue是MessageQueue類型,是Looper的成員變量
在Handler初始化方法賦值。下面我們看一下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;
    }

從這邊可以看到MessageQueue內(nèi)部存儲(chǔ)采用的是鏈表格式遍坟,本次方法的作用是將消息插入到鏈尾拳亿。整體邏輯是鏈表為空時(shí),將msg使用mMessages保存下來(lái)愿伴,然后喚醒肺魁。當(dāng)數(shù)據(jù)不為空時(shí),遍歷鏈表將數(shù)據(jù)插入到鏈尾隔节,并喚醒鹅经。
綜上所述Handler的sendMessage方法本質(zhì)上就是一次插入方法,目的是將消息插入到MessageQueue鏈尾官帘。
我們知道在子線程中發(fā)送消息之后則需要調(diào)用Looper.loop方法瞬雹,否則消息不生效。下面我們來(lái)看一下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;
            }

            ...
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
    }

上面是省略了的代碼刽虹,可以看到loop是個(gè)死循環(huán),唯一的退出操作是MessageQueue的next方法返回為空呢诬。當(dāng)Looper調(diào)用quit或者quitSafely方法的時(shí)候涌哲,next會(huì)返回為空,此時(shí)Looper會(huì)跳出循環(huán)尚镰。next方法是阻塞操作阀圾,當(dāng)沒有消息時(shí),next會(huì)一直阻塞在那里狗唉,導(dǎo)致loop方法也阻塞在那里初烘。直到next方法返回了新消息,Looper就會(huì)處理新消息分俯。當(dāng)獲取到信息時(shí)肾筐,會(huì)通過(guò)msg.target.dispatchMessage(msg)處理,target就是發(fā)送這條消息的Handler(Handler的enqueueMessage方法中將自身設(shè)置給msg(msg.target = this))缸剪,最終消息鏈回到了Handler的dispatchMessage方法中:

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

msg.callback是Runable類型吗铐,當(dāng)我們調(diào)用Handler.post(runable)的時(shí)候,最終是將Runable設(shè)置給Message的callback變量杏节。
dispatchMessage判斷msg.callback是否為空唬渗,不為空調(diào)用handleCallback方法:

private static void handleCallback(Message message) {
        message.callback.run();
}

就是調(diào)用了Runable的run方法典阵。而callback為空時(shí)則判斷mCallback是否為空,mCallback是Callback類型

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }

是Handler提供的一種非侵入式的回調(diào)镊逝,當(dāng)你不想重寫handler時(shí)可以設(shè)置Callback壮啊,在handleMessage處理消息并返回為true。
否則則調(diào)用自身handleMessage方法撑蒜,這個(gè)方法子類重寫后便可以處理消息他巨。

到這里handler調(diào)用原理就走通了,那么有幾個(gè)問(wèn)題减江?

1.View.post Handler.post 和普通的發(fā)送有什么區(qū)別
2.主線程直接創(chuàng)建Handler為什么不報(bào)錯(cuò)
3.loop阻塞了為什么不會(huì)影響主線程染突。
View.post
public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
}

View.post方法判斷有兩條,一如果AttachInfo不為空則直接使用它的handler.post處理Runable辈灼。
如果為空的話份企,則使用HandlerActionQueue.post。HandlerActionQueue是一個(gè)隊(duì)列巡莹,內(nèi)部維護(hù)這一個(gè)數(shù)組司志。
post方法只是將Runable封裝成HandlerAction放入到數(shù)組中

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

在View的dispatchAttachedToWindow方法中會(huì)調(diào)用HandlerActionQueue的executeActions方法,遍歷數(shù)組通過(guò)Handler.postDelayed處理信息

    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

我們之前分析過(guò)Handler的dispatchMessage方法降宅。其中針對(duì)Message的callback是否為空做了條件判定骂远。callback是Runable類型。
Handler.post方法

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

Handler.post方法其實(shí)也是調(diào)用了sendMessageDelayed來(lái)發(fā)送消息腰根,區(qū)別在于在獲取Message的時(shí)候?qū)unable設(shè)置給Message的callback屬性激才,在最終分發(fā)的方法dispatchMessage中依據(jù)callback是否為空來(lái)判定是否Runable的run方法還是Handler自己的回調(diào)方法。
到這邊我們可以解釋了View.post和Handler.post本質(zhì)上并沒有不同额嘿,都是依賴于Handler發(fā)送消息機(jī)制瘸恼,區(qū)別在于最終消息的回調(diào)方法不同。

在子線程中直接創(chuàng)建handler會(huì)報(bào)錯(cuò)册养,主線程直接創(chuàng)建Handler為什么不報(bào)錯(cuò)

Looper在主線程入口函數(shù)中調(diào)用了prepareMainLooper方法东帅,該方法是是創(chuàng)建主線程的Looper,因此我們?cè)谥骶€程中創(chuàng)建handler時(shí)球拦,Looper已經(jīng)存在靠闭,所以不會(huì)報(bào)錯(cuò)。

Android中為什么主線程不會(huì)因?yàn)長(zhǎng)ooper.loop()里的死循環(huán)卡死坎炼?

Android底層采用的是pipe/epoll機(jī)制愧膀,當(dāng)主線程的MessageQueue沒有消息時(shí),便阻塞在loop的queue.next方法的nativePullOnce里点弯,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài)扇调,直到下一條消息發(fā)送或者有事務(wù)到達(dá)時(shí),通過(guò)向pipe管道寫端寫入數(shù)據(jù)來(lái)喚醒主線程工作抢肛。這邊采用的epoll機(jī)制是一種IO多路復(fù)用的機(jī)制,可以同時(shí)監(jiān)聽多個(gè)描述符狼钮,當(dāng)某個(gè)描述符就緒(讀/寫就緒)就立即通知相應(yīng)程序進(jìn)行讀或者寫操作碳柱,本質(zhì)同步I/O,即讀寫是阻塞的。因此主線程大多數(shù)情況下處于休眠狀態(tài)熬芜,所以不會(huì)大量消耗CPU資源導(dǎo)致卡死莲镣。

epoll提供了三種方法
epoll_create(): 創(chuàng)建一個(gè)epoll實(shí)例并返回相應(yīng)的文件描述符(epoll_create1() 擴(kuò)展了epoll_create() 的功能)。
epoll_ctl(): 注冊(cè)相關(guān)的文件描述符使用
epoll_wait(): 可以用于等待IO事件涎拉。如果當(dāng)前沒有可用的事件瑞侮,這個(gè)函數(shù)會(huì)阻塞調(diào)用線程。
詳情參考https://my.oschina.net/u/3863980/blog/1933086

Activity是如何在死循環(huán)中執(zhí)行生命周期方法的鼓拧?

通過(guò)Handler機(jī)制執(zhí)行生命周期方法的半火。ActivityThread中有內(nèi)部類H繼承Handler。
Activity生命周期依靠looper.loop季俩,當(dāng)Handler接受到消息時(shí)钮糖,在其內(nèi)部handleMessage方法中處理對(duì)應(yīng)的生命周期方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酌住,一起剝皮案震驚了整個(gè)濱河市店归,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酪我,老刑警劉巖消痛,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異都哭,居然都是意外死亡秩伞,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門质涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)稠歉,“玉大人,你說(shuō)我怎么就攤上這事汇陆。” “怎么了带饱?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵毡代,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我勺疼,道長(zhǎng)教寂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任执庐,我火速辦了婚禮酪耕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轨淌。我一直安慰自己迂烁,他們只是感情好看尼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盟步,像睡著了一般藏斩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上却盘,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天狰域,我揣著相機(jī)與錄音,去河邊找鬼黄橘。 笑死兆览,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的塞关。 我是一名探鬼主播抬探,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼描孟!你這毒婦竟也來(lái)了驶睦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匿醒,失蹤者是張志新(化名)和其女友劉穎场航,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廉羔,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溉痢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了憋他。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孩饼。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖竹挡,靈堂內(nèi)的尸體忽然破棺而出镀娶,到底是詐尸還是另有隱情,我是刑警寧澤揪罕,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布梯码,位于F島的核電站,受9級(jí)特大地震影響好啰,放射性物質(zhì)發(fā)生泄漏轩娶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一框往、第九天 我趴在偏房一處隱蔽的房頂上張望鳄抒。 院中可真熱鬧,春花似錦、人聲如沸许溅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)闹司。三九已至娱仔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間游桩,已是汗流浹背牲迫。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留借卧,地道東北人盹憎。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铐刘,于是被迫代替她去往敵國(guó)和親陪每。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 開發(fā)Android一般都用遇到使用handler的情況镰吵,現(xiàn)在因?yàn)閞xjava的時(shí)候可能就減少了handler的使用...
    KIDNG_LGJ閱讀 218評(píng)論 0 0
  • 要分析Handler的原理檩禾,首先需要了解Message和Looper,所以我們先來(lái)分析一下Message和Loop...
    王小二的王閱讀 469評(píng)論 0 0
  • 簡(jiǎn)介# Handler 在 Android 開發(fā)中非常常見疤祭,它的常見用法相信只要稍微學(xué)過(guò)一些 Android 基礎(chǔ)...
    王尼小老板閱讀 855評(píng)論 2 17
  • 首先從Handler 的構(gòu)造方法開始盼产,Handler有幾種構(gòu)造方法,先從最開始的最普通的開始勺馆,Handelr ha...
    feary閱讀 680評(píng)論 0 2
  • 前言 在Android開發(fā)的多線程應(yīng)用場(chǎng)景中戏售,Handler機(jī)制十分常用 今天,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 469評(píng)論 0 0