Android Handler機制完全解析

1.為什么要引入Handler機制践瓷?

舉個栗子:通過網(wǎng)絡(luò)獲取數(shù)據(jù)然后顯示在TextView中耕驰,由于網(wǎng)絡(luò)通信屬于耗時操作炸卑,所以必須在子線程中完成既鞠,但是子線程中是不能更新UI的(特殊情況除外),為了解決以上問題盖文,Android引入了Handler機制嘱蛋,由Handler來負責與子線程進行通信,從而使子線程與主線程之間建立起協(xié)作的橋梁五续,使Android的UI更新問題得到完美的解決洒敏。

注:Android系統(tǒng)是在onResume方法回調(diào)之后檢查當前線程的,在此之前是可以在子線程中更新UI的疙驾,Google并不建議這么做凶伙,了解就好。

2.Handler原理

看圖說話:

Handler機制

先說說幾個重要的類:

  • Message:消息它碎,由MessageQueue統(tǒng)一隊列函荣,然后交由Handler處理。

  • MessageQueue:消息隊列扳肛,用來存放Handler發(fā)送過來的Message傻挂,并且按照先入先出的規(guī)則執(zhí)行。

  • Handler:處理者挖息,負責發(fā)送和處理Message金拒。

  • Looper:消息輪詢器,不斷的從MessageQqueue中抽取Message并執(zhí)行旋讹。

1.ActivityThread在主線程中啟動消息循環(huán)器Looper

在創(chuàng)建Activity之前殖蚕,當程序啟動的時候,系統(tǒng)會先加載ActivityThread這個類沉迹,在這個類的main函數(shù)中睦疫,調(diào)用Looper.prepareMainLooper()初始化Looper對象并創(chuàng)建消息隊列,然后調(diào)用Looper.loop()方法鞭呕,不斷的輪詢消息隊列中的消息蛤育。

package android.app;

public final class ActivityThread {

    public static void main(String[] args) {
        
        ...

        // 初始化Looper對象并創(chuàng)建消息隊列
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 開啟消息輪詢
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

看下Looper.prepareMainLooper()與Looper.loop()方法中發(fā)生了什么:

package android.os;

public final class Looper {
    
    /**
     * 為當前線程初始化Looper對象并創(chuàng)建消息隊列
     * 消息循環(huán)可以被終止
     */
    public static void prepare() {
        prepare(true);
    }

    /**
     * 為當前線程初始化Looper對象并創(chuàng)建消息隊列
     */
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 隊列與線程進行關(guān)聯(lián),防止其他線程對其訪問
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * 為UI線程初始化Looper對象并創(chuàng)建消息隊列
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /**
     * 啟動消息輪詢
     */
    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;

        // 消息循環(huán)
        for (;;) {
            // 獲取消息隊列中的消息,可能會阻塞
            Message msg = queue.next();
            if (msg == null) {
                return;
            }

            try {
                // 處理消息
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            // 回收消息
            msg.recycleUnchecked();
        }
    }
    
    /**
     * 獲取當前線程的Looper對象
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    /**
     * 獲取當前線程的消息隊列
     */
    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

    /**
     * Looper構(gòu)造方法瓦糕,初始化消息隊列與當前線程
     */
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
}

到這里底洗,Handler發(fā)送和接收消息的準備工作就已經(jīng)完成了,接下來讓我們來初始化一個Handler試試吧咕娄!

2.在主線程中創(chuàng)建Handler

通常我們都會在主線程中創(chuàng)建Handler來接收子線程的消息亥揖,看下Handler是如何創(chuàng)建的:

// 定義一個Handler對象,并實現(xiàn)handleMessage方法
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 在此接收子線程發(fā)送的消息
    }
};

Handler中的構(gòu)造方法最終都會調(diào)用Handler(Callback callback, boolean async)方法圣勒,獲取當前線程的Lopper對象费变,與之關(guān)聯(lián),然后獲取消息隊列圣贸。

package android.os;

public class Handler {

    public Handler(Callback callback, boolean async) {
        
        ...

        // 獲取當前線程的Looper對象
        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;
    }
}

3.使用sendMessage發(fā)送消息

package android.os;

public class Handler {

    /**
     * 發(fā)送消息
     */
    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    
    /**
     * 延時發(fā)送消息
     */
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        
        // 計算消息入列的時間
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    /**
     * 發(fā)送定時消息
     */
    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);
    }
}

看下enqueueMessage方法:

boolean enqueueMessage(Message msg, long when) {
    
    ...

    synchronized (this) {
        
        ...

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        
        // 如果有新消息首次加入隊列或者新消息的延遲時間小于隊列中首個消息的延遲時間挚歧,就將新消息放在隊列頭部
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 如果隊列不為空,將新消息按照時間排序放入隊列
            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;
            prev.next = msg;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

4.消息隊列將消息分發(fā)給Handler處理

發(fā)送消息到消息隊列后吁峻,Looper就會在消息隊列中按順序取出消息分發(fā)給Handler處理滑负,看下dispatchMessage方法,如果callback對象為null的話用含,就會回調(diào)handleMessage方法矮慕,如果不為空會回調(diào)callback的run方法。我們平時使用的sendMessage方法沒有設(shè)置callback啄骇,所以會回調(diào)handleMessage方法凡傅,如果使用post(Runnable callback)方法,則會回調(diào)callback的run方法肠缔。

package android.os;

public class Handler {

    /**
     * 消息分發(fā)
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 回調(diào)callback的run方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 回調(diào)handleMessage方法
            handleMessage(msg);
        }
    }
}

OK夏跷,通過對源碼的分析,Handler的原理已經(jīng)學習完了明未,接下來讓我們來學習一下如何在子線程中創(chuàng)建Handler槽华。

3.在子線程中創(chuàng)建Handler

通常我們使用Handler都是從子線程向主線程發(fā)送消息,如果需要主線程通知子線程做一些耗時邏輯趟妥,或者子線程之間進行通信的話猫态,直接在子線程中創(chuàng)建Handler會拋出異常:

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

看下源碼:

package android.os;

public class Handler {

    public Handler(Callback callback, boolean async) {
        
        ...

        // 獲取當前線程的Looper對象
        mLooper = Looper.myLooper();
        // 如果looper為空則會拋出異常
        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;
    }
}

由于每個線程都用自己的Looper,這個Looper可以為null披摄,系統(tǒng)默認在主線程中創(chuàng)建了Looper亲雪,但在子線程中需要手動設(shè)置,否則就會拋出異常疚膊。

代碼實現(xiàn):

new Thread("子線程") {
    @Override
    public void run() {
        super.run();
        // 初始化Looper對象并創(chuàng)建消息隊列
        Looper.prepare();
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i(TAG, (String) msg.obj + "___" + Thread.currentThread().getName());
            }
        };
        Message message = new Message();
        message.obj = "消息";
        handler.sendMessage(message);
        // 開始輪詢
        // 由于loop方法是死循環(huán)义辕,所以要寫在最后
        Looper.loop();

    }
}.start();

還有沒有更優(yōu)雅的方式呢?答案是肯定的寓盗,為了解決上面的問題灌砖,Android系統(tǒng)為我們提供了HandlerThread類璧函,看下源碼:

package android.os;

public class HandlerThread extends Thread {

    ...
   
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            // 初始化Looper對象并創(chuàng)建消息隊列
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        // 開啟消息輪詢
        Looper.loop();
        mTid = -1;
    }
}

有沒有很熟悉,HandlerThread類的run方法中做了和ActivityThread類中一樣的處理基显,這樣就不用再手動初始化Looper了蘸吓,nice,接下來讓我們用HandlerThread來實現(xiàn)一下:

HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper()){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.i(TAG, (String) msg.obj + "___" + Thread.currentThread().getName());            }
};

Message message = new Message();
message.obj = "消息";
handler.sendMessage(message);

4.寫在最后

歡迎同學們吐槽評論撩幽,如果你覺得本篇博客對你有用库继,那么就留個言或者點下喜歡吧(^-^)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窜醉,隨后出現(xiàn)的幾起案子制跟,更是在濱河造成了極大的恐慌,老刑警劉巖酱虎,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異擂涛,居然都是意外死亡读串,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門撒妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恢暖,“玉大人,你說我怎么就攤上這事狰右〗芪妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵棋蚌,是天一觀的道長嫁佳。 經(jīng)常有香客問我,道長谷暮,這世上最難降的妖魔是什么蒿往? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮湿弦,結(jié)果婚禮上瓤漏,老公的妹妹穿的比我還像新娘。我一直安慰自己颊埃,他們只是感情好蔬充,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著班利,像睡著了一般饥漫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罗标,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天趾浅,我揣著相機與錄音愕提,去河邊找鬼。 笑死皿哨,一個胖子當著我的面吹牛浅侨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播证膨,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼如输,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了央勒?” 一聲冷哼從身側(cè)響起不见,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崔步,沒想到半個月后稳吮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡井濒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年灶似,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑞你。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡酪惭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出者甲,到底是詐尸還是另有隱情春感,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布虏缸,位于F島的核電站鲫懒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刽辙。R本人自食惡果不足惜刀疙,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扫倡。 院中可真熱鬧谦秧,春花似錦、人聲如沸撵溃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘挑。三九已至集歇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間语淘,已是汗流浹背诲宇。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工际歼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姑蓝。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓鹅心,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纺荧。 傳聞我的和親對象是個殘疾皇子旭愧,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

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