Android 的消息機制 - Handler

為了更好的理解 Looper 的工作原理岖研,我們需要對 ThreadLocal 進行了解,如果對 ThreadLocal 沒有了解的童鞋,可以參看 ThreadLocal 原理

概述

Handler 作為日常開發(fā)的必備,不可避免就要涉及這方面的知識拦英。從開發(fā)者角度來說,Handler 是 Android 消息機制的上層接口测秸,使得開發(fā)的時只需與 Handler 交互即可。Handler 使用也很簡單灾常,能夠輕松將一個任務(wù)切換到 Handler 所在的線程中執(zhí)行霎冯。

很多人認(rèn)為Handler的作用就是更新UI,的確沒錯钞瀑,但是更新UI僅僅是Handler的一個特殊的使用場景沈撞。具體來說,就是有時候需要在子線程做一些耗時操作雕什,比如說訪問網(wǎng)絡(luò)或者耗時的I/O操作缠俺,當(dāng)這些耗時操作完成時显晶,程序的UI進行相應(yīng)的改變。由于安卓開發(fā)規(guī)范的限制壹士,我們不能在子線程中訪問UI控件磷雇,因為UI的控件是線程非安全的,這個時候通過Handler就可以將更新UI的操作切換到主線程中執(zhí)行躏救。

Android 的消息機制主要是指 Handler 的運行機制唯笙。事實上,Handler盒使,Looper崩掘,MessageQueue 是一套運行體制而出現(xiàn)的,MessageQueue 是一個消息隊列少办,以隊列形式提供插入和刪除苞慢,主要用于消息的存儲,內(nèi)部實現(xiàn)是采用單鏈表的形式來組織 Message英妓。Looper 用于處理消息挽放,Looper 內(nèi)部會以無限循環(huán)去查是否有新的 Message ,有則處理鞋拟,沒有就等待骂维。需要特殊說明的是,Looper 內(nèi)部是使用 ThreadLocal 實現(xiàn)的贺纲,由于ThreadLocal 可以在每一個線程中互不干擾的存取數(shù)據(jù)航闺,所以通過ThreadLocal 就可以輕松獲取每個線程的 Looper蹭越。

Message :android.os.Message是定義一個Messge包含必要的描述和屬性數(shù)據(jù)也颤,并且此對象可以被發(fā)送給android.os.Handler處理堆生。屬性字段:arg1白华、arg2腥椒、what方妖、obj匣缘、replyTo等窘哈;其中arg1和arg2是用來存放整型數(shù)據(jù)的澄成;what是用來保存消息標(biāo)示的胧洒;obj是Object類型的任意對象;replyTo是消息管理器墨状,會關(guān)聯(lián)到一個handler卫漫,handler就是處理其中的消息。通常對Message對象不是直接new出來的肾砂,只要調(diào)用handler中的obtainMessage方法來直接獲得Message對象列赎。

需要特殊說明的是,線程是默認(rèn)沒有 Looper 的镐确,如果需要使用 Handler 就必須為線程創(chuàng)建Looper包吝,但是APP 的主線程中饼煞,即 ActivityThread ,在主線程被創(chuàng)建的時候就會初始化 Looper诗越。另外砖瞧,在沒有Looper 的線程創(chuàng)建 Handler 也會失敗。

Handler 工作過程

Hnadler 的工作流程

使用案例

話不多說掺喻,上例子:

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private String TAG = "MainActivity";
    private int i = 0;

    Handler mHandler = new Handler(){
        /**
         * handleMessage接收消息后進行相應(yīng)的處理
         * @param msg
         */
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what==1){
                textView.setText(msg.arg1+"");
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);
    }
    public void onClick(View v){
        ++i;
        //創(chuàng)建新的線程
        new Thread(){
            @Override
            public void run() {
                super.run();
                doSendMsg();
            }
        }.start();
    }

    /**
     * 在子線程中做耗時操作芭届,完成之后,通知Handler更新UI
     */
    private void doSendMsg(){
        try {
            Thread.sleep(1000);//模擬耗時操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Message message = Message.obtain();
        message.arg1 = i;
        message.what = 1;
        mHandler.sendMessage(message);
    }

}

原理分析

代碼版本 : Android API25

Message

生成一個 Message

前面講過 Message 通常不是 new 出來的感耙,而是通過調(diào)用 Handler 的obtainMessage() 得到一個新的 Message:

 public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

而 Handler 就調(diào)用 Message 的 obtain(Handler h) 方法:

public final class Message implements Parcelable {
   
    public int what;  // 讓接收者知道這是什么
    public int arg1;  // 存儲 int 用于傳遞過去
    public int arg2;  
    public Object obj;  // 傳遞一個對象過去
    public Messenger replyTo;  // 可以發(fā)送對此消息的回復(fù)褂乍。具體如何使用取決于發(fā)送者和接收者
    public int sendingUid = -1;

    /*package*/ static final int FLAG_IN_USE = 1 << 0;  // 標(biāo)記消息被使用
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; // 標(biāo)記消息是異步的
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;  
    /*package*/ int flags;
    /*package*/ long when;     // Message 的執(zhí)行時間 重要!<磁稹逃片!
    /*package*/ Bundle data;    
    /*package*/ Handler target;   // target 負(fù)責(zé)處理該消息
    /*package*/ Runnable callback;  // Runnable 類型的 callback
    /*package*/ Message next;     // 下一條消息,因為消息隊列是鏈?zhǔn)酱鎯Φ?
    private static final Object sPoolSync = new Object();  // 控制并發(fā)訪問
    private static Message sPool;   // 回收池的頭結(jié)點
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;

    private static boolean gCheckRecycle = true;

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;  // 將頭結(jié)點賦值給Message
                sPool = m.next;        // 將頭結(jié)點更新為下一個節(jié)點
                m.next = null;     // 斷掉以前頭節(jié)點與當(dāng)前節(jié)點聯(lián)系
                m.flags = 0; // clear in-use flag
                sPoolSize--;      // 將回收池的數(shù)量減一
                return m;
            }
        } 
        return new Message();
    }

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;     // 注意只酥,這里將 Handler 保存在了 target 中褥实,后面會調(diào)用target來處理這個Message
        return m;
    }

// ...
}

到了最后就是Message 的 obtain() 方法,從 global pool 返回一個新的Message實例裂允。 允許我們在許多情況下避免分配新對象损离。而這個 global pool 呢,其實就是一個單鏈表绝编,從頭結(jié)點取一個 Message 僻澎,如果沒有就 new 一個 Message。而這個單鏈表呢十饥,就是講無用需要回收的 Message 組織起來的窟勃。the global pool 其實就是使用靜態(tài)常量組織了一些無用了的 Message,組織的數(shù)據(jù)結(jié)構(gòu)就是單鏈表逗堵。

看源碼就知道秉氧,重載了多個obtain 方法,其實就是把上述可選參數(shù)配置一下蜒秤,然后調(diào)用 obtain() 得到一個Message汁咏。

回收 Message

可能看了生成有點懵,那我們提前看一下回收作媚,就好一點了梆暖, Message 的 源碼:

    public void recycle() {
        if (isInUse()) {           // isInUse() return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;   // 在用就不回收
        }
        recycleUnchecked();  // 直接回收
    }

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;    // 設(shè)置為沒有使用
        what = 0;             // 抹去數(shù)據(jù)
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;  // 將當(dāng)前 Message 的下一個設(shè)置為以前的頭結(jié)點
                sPool = this;   // 更新頭結(jié)點為當(dāng)前節(jié)點
                sPoolSize++;
            } // 如果到達(dá)MAX_POOL_SIZE數(shù)量,這個Message就會因為沒有與引用鏈相連而被GC回收
        }
    }

回收還是很簡單的掂骏,就是先檢驗是否在使用,如果不是厚掷,抹去上面的數(shù)據(jù)弟灼,將 Message 加到我稱為 回收池 的鏈表里级解。需要注意的是,這里如果回收池數(shù)量到了上限田绑,這個Message就會因為沒有與引用鏈相連而被GC回收勤哗。

MessageQueue

MassageQueue 叫做消息隊列,通過一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表掩驱,而且單鏈表在插入和刪除上比較有優(yōu)勢芒划。MessageQueue 主要包含兩個操作:插入 和 讀取。插入讀取對應(yīng)的方法分別是enqueueMessage(Message msg, long when) 和 next()欧穴。

工作流程

這里的工作流程先是講一下例子里面的工作流程民逼,其次補充一些必要的流程,如回收等涮帘。

UI線程 Looper 的創(chuàng)建

我們知道 UI 線程是在 ActivityThread 中創(chuàng)建的拼苍,這個函數(shù)就是整個 APP 的入口。接下來就是 ActivityThread 中的 main:

    public static void main(String[] args) {
        ... 
        Looper.prepareMainLooper();  // 1. 創(chuàng)建UI線程的 Looper

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

        if (sMainThreadHandler == null) {
            // UI 線程的Handle
            // getHandler() 得到的是 ActivityThread.H (extends Handler)
            sMainThreadHandler = thread.getHandler(); 
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();    //2. 執(zhí)行消息循環(huán)

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

看到?jīng)]有调缨,通過1,2步的配置疮鲫,這時候 UI 線程中 Looper 其實已經(jīng)跑起來了,在程序中就已經(jīng)可以使用 Handler 了弦叶。而子線程卻默認(rèn)沒有配置俊犯。

需要注意的 UI 線程使用的 prepareMainLooper() 來準(zhǔn)備 Looper,但是這個方法雖然是 public 的伤哺,但是這是專門為 UI 線程量身定做的燕侠,我們絕對不可以使用,我們準(zhǔn)備Looper可以使用 Looper 的 prepare()就好默责。

prepareMainLooper()

接下來我們來一步一步分析 (Looper 源碼:)

    public static void prepareMainLooper() {
        prepare(false);  // new 一個 Looper 放到該線程的ThreadLocalMap中
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // 將 UI 線程的 Looper 放到靜態(tài)常量 sMainLooper 中贬循,那么隨時隨地都可以new 出主線程的 Handler
            sMainLooper = myLooper();  // myLooper() return sThreadLocal.get();
        }
    }

    public static void prepare() {
        prepare(true);  // 子線程中默認(rèn)是可以銷毀消息隊列的
    }

    private static void prepare(boolean quitAllowed) {  // True if the message queue can be quit.
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // sThreadLocal 是在類定義的時候就初始化了的 static final ThreadLocal<Looper>
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();  // 得到當(dāng)前線程的 Looper
    }

很簡單,結(jié)合注釋桃序,應(yīng)該都懂了杖虾,就是 new 了一個 Looper ,放到了主線程的 ThreadLocalMap 中媒熊。那 new 的時候干了什么呢奇适?

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

原來是這樣,在 new Looper 的時候就創(chuàng)建了 UI 線程的消息隊列芦鳍,并且指定不可以刪除嚷往。

    // True if the message queue can be quit.
    private final boolean mQuitAllowed;

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

隊列是否可以刪除,一直向下傳遞柠衅,最后原來是保存在這里皮仁。但是這里還有一個 Native 方法,是干嘛的呢?筆者認(rèn)為是得到了這個對象所在線程的引用贷祈。

到這里趋急,ActivityThread 中的第一步就算是完成了。由于 sMainThreadHandler 的后期使用涉及復(fù)雜势誊,就留到后面講解呜达,這里用 例子中的 Handler 代替講解,最后執(zhí)行的 Handler 是一樣的粟耻,但是 ActivityThread.H(sMainThreadHandler )封裝了其他東西查近。

創(chuàng)建 Handler

看著例子中的 Handler 是直接 new 出來的,那我們看一下 Handler 的無參構(gòu)造方法:

public class Handler {
    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
       ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {        // ***重要<访ΑK!
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
}

我們看到 Handler 在構(gòu)造方法中饭玲,通過 Looper.myLooper() 得到當(dāng)前線程(UI 線程)的Looper侥祭,并且保存到本地 final 變量 mLooper 中。

要知道茄厘,消息隊列被封裝在 Looper 中矮冬,而每一個 Looper 又會關(guān)聯(lián)一個線程(Looper 通過 ThreadLocal 封裝),最終等于每一個消息隊列都會關(guān)聯(lián)一個線程次哈。同時胎署,由上代碼可知,每個 Handler 也都會關(guān)聯(lián)一個消息隊列窑滞。在這里需要注意琼牧,Looper 和 MessageQueue 并沒有與 Handler關(guān)聯(lián),而是Handler 與 Looper 和 MessageQueue 建立聯(lián)系哀卫。

Looper.loop()

創(chuàng)建了Looper之后巨坊,就要執(zhí)行消息循環(huán)了,我們知道此改,通過 Handler 來 Post 消息給消息隊列趾撵,那么怎么處理呢?那就是最開始第二步的 Looper.loop() 中的共啃。

    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;  // 1. 獲取消息隊列

        // 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();

        for (;;) {       // 2. 消息循環(huán)
            Message msg = queue.next(); // might block可能阻塞 3. 獲取消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);  // 4. 處理消息
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();

            msg.recycleUnchecked(); // 回收
        }
    }

我們可以看到 loop 方法中實際上就是建立一個死循環(huán)占调,然后通過從消息隊列中逐個取出消息,最后進行處理移剪,至到取到 null 值才退出究珊。這里并沒有任何的阻塞,那我消息取完了就退出了么纵苛?不剿涮,原理請看 MassageQueue 的 next() :

 Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        // 注意言津,以下的代碼都在循環(huán)體里面
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);  // 重要!aB病纺念!

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();   // 從開機到現(xiàn)在的毫秒數(shù)(手機睡眠的時間不包括在內(nèi))
                Message prevMsg = null;      // 前驅(qū)結(jié)點
                Message msg = mMessages;  // 頭結(jié)點

                // 如果沒有 Handler,就在列表中找下一個同步的 Message 來執(zhí)行想括。
                if (msg != null && msg.target == null) {  
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

                if (msg != null) {   // 如果頭結(jié)點不為空
                    if (now < msg.when) {   // 如果還沒有到執(zhí)行時間
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                         // 設(shè)置喚醒時間,距離多久執(zhí)行 與 int 最大值 2^31 - 1 作比較
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 注意這時候已經(jīng)到執(zhí)行時間了
                        // Got a message.
                        mBlocked = false;
                        // 從消息隊列中取出這個 Message
                        if (prevMsg != null) {  // 如果前驅(qū)結(jié)點不為 null烙博,
                            prevMsg.next = msg.next;   // 請參看 next() 圖 1
                        } else {
                            mMessages = msg.next;  //  將頭結(jié)點后移
                        }
                        msg.next = null;   // 斷掉要處理的 Message 與 消息隊列的聯(lián)系
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();   // 標(biāo)記為使用
                        return msg;
                    }
                } else {  // 頭結(jié)點為空
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            } //同步代碼塊結(jié)束

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

    // Disposes of the underlying message queue.
    // Must only be called on the looper thread or the finalizer.
    private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }

代碼雖然很長瑟蜈,但是我們還是得看啊,其實就是取出單鏈表(我們前面已說過渣窜,MessageQueue其實是一個單鏈表結(jié)構(gòu))中的頭結(jié)點铺根,然后修改對應(yīng)指針,再返回取到的頭結(jié)點而已乔宿。因為這里采用的是無限循環(huán)位迂,所以可能會有個疑問:該循環(huán)會不會特別消耗CPU資源?其實并不會详瑞,如果messageQueue有消息掂林,自然是繼續(xù)取消息;如果已經(jīng)沒有消息了坝橡,此時該線程便會阻塞在該next()方法的 nativePollOnce() 方法中泻帮,主線程便會釋放CPU資源進入休眠狀態(tài),直到下個消息到達(dá)或者有事務(wù)發(fā)生(設(shè)置的nextPollTimeoutMillis到了)時计寇,才通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作锣杂。這里涉及到的是Linux的pipe/epoll機制,epoll機制是一種IO多路復(fù)用機制番宁,可以同時監(jiān)控多個描述符元莫,當(dāng)某個描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進行讀或?qū)懖僮鞯海举|(zhì)同步I/O踱蠢,即讀寫是阻塞的。

next() 圖 1

總結(jié):
通過 Looper.loop() 來創(chuàng)建 Looper 對象( 消息隊列封裝在 Looper 對象中 )播聪,并且保存在 sThreadLocal 中朽基,然后通過 Looper.loop() 來執(zhí)行消息循環(huán)。

說了取离陶,當(dāng)然接著就是分發(fā)了稼虎。我們調(diào)用msg.target.dispatchMessage(msg) 來執(zhí)行 Message 。在創(chuàng)建 Message 的過程中招刨,傳過來的 Handler 的引用就被保存在了Message中(最上面有代碼和講解)霎俩。接下來看一下 Handler 的處理:

    final Callback mCallback;

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

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

    private static void handleCallback(Message message) {
        message.callback.run();   // 此時是在 Handler 所在線程中執(zhí)行
    }

這里面也特別簡單,大家都知道當(dāng)我們在 Handler post()消息的時候是使用 Runnable,而 sendMessage() 是自己構(gòu)建的 Message打却。這里首先判斷 Message 類型杉适,如果是 Runnable ,就調(diào)用run運行柳击,如果不是猿推,先判斷創(chuàng)建 Handler 的時候是否設(shè)置回調(diào),設(shè)置了就調(diào)用回調(diào)中的處理方法 handleMessage(msg)捌肴,如果沒有就使用默認(rèn)的 handleMessage(msg)蹬叭,這時候的這個方法大多數(shù)時候都會被重寫,就像例子一樣状知。

這里我們可以看到秽五,在分發(fā)消息時三個方法的優(yōu)先級分別如下:

  • Message的回調(diào)方法優(yōu)先級最高,即message.callback.run()饥悴;
  • Handler的回調(diào)方法優(yōu)先級次之坦喘,即Handler.mCallback.handleMessage(msg);
  • Handler的默認(rèn)方法優(yōu)先級最低西设,即Handler.handleMessage(msg)瓣铣。


    Handler 工作原理.png

使用 Handler 來 sendMessage(Message msg)

既然提到 post() 和 sendMessage(),那么下面就講解一下它是如何將一個 Message 加到隊列中的济榨。

現(xiàn)講解 sendMessage() 吧坯沪,例子就是最上面的例子,那我們接著看 Handler 的源碼吧:

    /**
     * Pushes a message onto the end of the message queue after all pending messages
     * before the current time. It will be received in {@link #handleMessage},
     * in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);  // 默認(rèn)延時為 0
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {  // 校驗延時擒滑,因為延時只能 >= 0
            delayMillis = 0;
        }
        // SystemClock.uptimeMillis() 從開機到現(xiàn)在的毫秒數(shù)(手機睡眠的時間不包括在內(nèi))
        // SystemClock.uptimeMillis() + delayMillis算出來就是更新時間
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  

    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        // 這個值是通過從Looper 賦值過來的腐晾,就意味著是持有和Looper 中相同的引用
        // Looper 中的修改的話mQueue,這個值也會被修改
        // 當(dāng)調(diào)用 Looper.quit() 的時候丐一,這個值就被置空了的藻糖。
        MessageQueue queue = mQueue;  
        if (queue == null) {   // 因為需要發(fā)送到 MassageQueue中,所以不能為空
            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;   // 保存 Message 的 target
        if (mAsynchronous) {
            msg.setAsynchronous(true);  
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

一直往下調(diào)用了三個方法才到底库车,還好都簡單巨柒,來看一下。將設(shè)置的時延轉(zhuǎn)化成應(yīng)該被執(zhí)行的時間柠衍,拿到關(guān)聯(lián)的消息隊列洋满,隨后保存 Message 的 target,然后調(diào)用消息隊列的 enqueueMessage(msg, uptimeMillis)珍坊,將這個Message 加入隊列牺勾。那怎么加的么?接下來就是 MessageQueue 的 enqueueMessage(Message msg, long when)

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {  // 一個新的 Message 是不會 InUse 的阵漏,在回收的時候設(shè)置為沒在使用的
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {   // 獲得自身的同步鎖
            if (mQuitting) {  // MessageQueue 正在退出
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();  // 將Message實例回收
                return false;
            }

            msg.markInUse();   // flags |= 1 標(biāo)記正在使用
            msg.when = when;   // 設(shè)置要插入 Message 的執(zhí)行時間
            Message p = mMessages;  // mMessages為頭結(jié)點
            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();  //記得看上面的英文注釋 默認(rèn)為 false
                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);  // 喚醒線程的 Native 方法
            }
        }
        return true;
    }

看 enqueueMessage 的實現(xiàn)驻民,它的主要操作就是單鏈表的插入操作翻具,并且這鏈表是以執(zhí)行時間 when 作為順序的。需要重提的是回还,這時候的 when 已經(jīng)轉(zhuǎn)換成了距離開機到執(zhí)行的毫秒數(shù)裆泳。

  1. 校驗。是檢測target是否存在柠硕,因為Message. targe 是用來處理這個 Message 的工禾,所以一定要有target,其次判斷當(dāng)前 Message 是否正在被使用蝗柔,然后驗證當(dāng)前 MessageQueue 是不是已經(jīng)被 quit 了(mQuitting)帜篇,驗證通過過后就是正式的插入操作。
  2. 配置诫咱。設(shè)置 Message 該有的屬性,msg.markInUse(); msg.when = when;
  3. 這個方法比較巧妙洪灯,將幾種具體情況用一份代碼解決了坎缭,但是都可以歸結(jié)為:在頭結(jié)點之前插入結(jié)點∏┕常看到這里應(yīng)該注意到掏呼,如果插入的是需要非延時 Message,并且線程阻塞了铅檩,就會調(diào)用 nativeWake(mPtr) 喚醒線程憎夷。
  • 當(dāng)還沒有鏈表的時候(p == 0)
  • 消息的執(zhí)行時間 比 里面的消息還要早(when == 0 || when < p.when)
  1. 在鏈表中間或最后插入。循環(huán)遍歷鏈表昧旨,拿到鏈表合適的 Message拾给,然后再將新 Message 插入。由于還沒有到消息的處理時間兔沃,就不會喚醒線程蒋得。此插入過程如下:

使用 Handler 來 post()

先上一個使用的例子:

private Handler mHandler;//全局變量
@Override
protected void onCreate(Bundle savedInstanceState) {
    mHandler = new Handler();
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);//在子線程有一段耗時操作,比如請求網(wǎng)絡(luò)
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mTestTV.setText("This is post");//更新UI
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
}

例子很簡單,就不講解了乒疏,接下來就來看一下 Handler 怎么 post 的吧:

    /**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is 
     * attached. 
     *  
     * @param r The Runnable that will be executed.
     * 
     * @return Returns true if the Runnable was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();  // 得到一個新的 Message
        m.callback = r;  // 是使用 post 的 Runnable 才會保存有這個參數(shù)
        return m;
    }

眼尖的童鞋估計看到就要easy了额衙。在 post 一開始就調(diào)用了 sendMessageDelayed(Message msg, long delayMillis),是不是很眼熟怕吴,對的窍侧,前面sendMessage(Message msg)一開始也是調(diào)用這個方法。這就是非延時消息的插入转绷。后面具體如何插入請參看上一條伟件。

同樣的,如果是使用 postDelayed(Runnable r, long delayMillis) 呢暇咆?使用例子請參看 面試題:Handler機制锋爪,Handler除了線程通信還有什么作用 中的作用二的第二種實現(xiàn)方式丙曙。轉(zhuǎn)回來看 Handler 的 postDelayed :

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

原來啊,大家都用的一套其骄,還是調(diào)用的 sendMessageDelayed 方法亏镰,只不過延遲時延不再為默認(rèn)的 0. 這時候就是延遲消息的插入。

工作原理時間圖.jpg

退出消息循環(huán)

退出消息循環(huán)有兩種方式拯爽,分別是Looper 的 quit()quitSafely()

   public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

這還是很簡單索抓,直接調(diào)用了 MassageQueue 的 qiut(boolean safe) :

    void quit(boolean safe) {
        if (!mQuitAllowed) {  // 主線程設(shè)置不允許退出,其他子線程默認(rèn)設(shè)置的true
           throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {  // 默認(rèn)為 false毯炮,表示是否正在退出消息隊列
                return;
            }
            mQuitting = true;  // 全局唯一一次賦值逼肯,表示正在退出消息隊列

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

首先判斷是否是主線程,因為主線程的消息隊列不允許退出桃煎,然后判斷當(dāng)前線程是否正在退出篮幢。值得注意的是,mQuitting 是 MassageQueue 的成員變量为迈,是擁有默認(rèn)值的三椿,默認(rèn)值是 false。如果不是正在退出消息隊列葫辐,則將其標(biāo)志為 正在退出搜锰,注意,全局就只有這里修改了 mQuitting 的值耿战。然后判斷根據(jù)要求是否安全移除 MessageQueue蛋叼。

    private void removeAllMessagesLocked() {
        Message p = mMessages;  // 頭結(jié)點
        while (p != null) {
            Message n = p.next;     // 下一個結(jié)點
            p.recycleUnchecked();  // 前面講過,就是將數(shù)據(jù)抹去剂陡,放入回收的那個鏈表
            p = n;       
        }
        mMessages = null;   // 將MessageQueue 中保存的頭結(jié)點設(shè)置為 null
    }

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();  //  從開機到現(xiàn)在的毫秒數(shù)(手機睡眠的時間不包括在內(nèi))狈涮; 
        Message p = mMessages;   // 頭結(jié)點
        if (p != null) {     
            // 如果頭結(jié)點設(shè)置的延遲時間 > 大于當(dāng)前時間
            // 注意  這里比較的時候都是用的從開機到現(xiàn)在的毫秒數(shù)
            // 這里意味著還沒有到設(shè)置的執(zhí)行時間
            if (p.when > now) {    
                removeAllMessagesLocked();  // 直接移除全部還未到執(zhí)行時間的 Message
            } else {
                Message n;
                for (;;) {
                    n = p.next;  // 拿到下一個節(jié)點
                    if (n == null) {  // 結(jié)束標(biāo)志:沒有下一個
                        return;   
                    }
                    if (n.when > now) {  // 還沒有到設(shè)置的執(zhí)行時間,退出循環(huán)
                        break;
                    }
                    p = n;
                }
                // 這時候得到的節(jié)點 p 是沒有到設(shè)置的執(zhí)行時間的前一個節(jié)點
                // n 是沒有到設(shè)置的執(zhí)行時間的節(jié)點
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

removeAllMessagesLocked()直接將MessageQueue 中的 Message 全部回收掉鹏倘。無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息(delayMillis == 0)薯嗤。

removeAllFutureMessagesLocked() 方法呢,就是只會清空MessageQueue消息池中所有的延遲消息纤泵,并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理骆姐,在這個方法中,表現(xiàn)為直接return 捏题,然后因為隊列有Message玻褪,所以相應(yīng)的 dispatchMessage(msg) 會調(diào)用。

quitSafely相比于quit方法安全之處在于清空消息之前會派發(fā)所有的非延遲消息公荧。

面試題

handler發(fā)消息給子線程带射,looper怎么啟動?

發(fā)消息就是把消息塞進去消息隊列循狰,looper在應(yīng)用起來的時候已經(jīng)就啟動了窟社,一直在輪詢?nèi)∠㈥犃械南ⅰ?/p>


為什么在子線程中創(chuàng)建Handler會拋異常券勺?

首先看如下代碼:

        new Thread(){
            Handler handler = null;

            @Override
            public void run() {
                handler = new Handler();
            }
        }.start();

前面說過,Looper 對象是 ThreadLocal 的灿里,即每個線程都有自己的 Looper关炼,這個 Looper 可以為空。但是匣吊,當(dāng)你在子線程中創(chuàng)建 Handler 對象時儒拂,如果 Looper 為空,那就會拋出異常色鸳。源碼解釋一下:

public class Handler {
       ...
   public Handler(Callback callback, boolean async) {
       ...
       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;
   }
  ...
}

從上述程序中社痛,我們可以看到,當(dāng) mLooper 對象為空的時候命雀,拋出了異常蒜哀。這是因為該線程中的 Looper 對象還沒有創(chuàng)建,因此 sThreadLocal.get() 會返回 null吏砂。Handler 的原理就是要與 MassageQueue 建立關(guān)聯(lián)凡怎,并且將消息投遞給MassageQueue,如果連 MassageQueue 都沒有赊抖,那么 Handler 就沒有存在的必要,而 MassageQueue 又被封裝在 Looper 中寨典,因此氛雪,創(chuàng)建 Handler 時 Looper 一定不能為空。解決方法:

     new Thread(){
            Handler handler = null;
            @Override
            public void run() {
                Looper.prepare();   // 1. 為當(dāng)前線程創(chuàng)建 Looper耸成,并會綁定到 ThreadLocal 中
                handler = new Handler();
                Looper.loop();  // 2. 啟動消息循環(huán)
            }
        }.start();

在UI線程為什么可以直接使用呢报亩,就是因為在 ActivityThread中默認(rèn)幫你執(zhí)行了 1,2步了的井氢。


Handler為什么loop是死循環(huán)弦追。

在android中如果主線程(UI線程)中進行耗時操作會引發(fā)ANR(Application Not Responding)異常,產(chǎn)生ANR的原因一般有兩種:

  1. 當(dāng)前的事件沒有機會得到處理(即主線程正在處理前一個事件花竞,沒有及時的完成或者looper被某種原因阻塞住了)

  2. 當(dāng)前的事件正在處理劲件,但沒有及時完成

比如onCreate()中進行了耗時操作,導(dǎo)致點擊约急、觸摸等不響應(yīng)零远,就會產(chǎn)生ANR。為了避免ANR異常厌蔽,android使用了Handler消息處理機制牵辣,讓耗時操作在子線程運行,需要UI線程進行處理的操作給UI線程發(fā)送消息奴饮。

我們知道Handler發(fā)消息給UI線程就可以處理消息纬向,UI線程維護著一個Looper和一個消息隊列择浊,Looper不停的拿消息隊列的消息去分發(fā)處理。到這里問題來了:如果這么做的話逾条,UI線程豈不是要一直死循環(huán)輪詢消息隊列拿消息琢岩?死循環(huán)不是造成ANR嗎?

是的膳帕,確實是死循環(huán)粘捎,但是 ANR 還得另說。

ActivityThread 的 main()

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    ...
    Looper.loop();
 
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.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;
 
    // 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();
 
    for (;;) {
        Message msg = queue.next(); // might block
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}

Looper.loop()是在死循環(huán)處理消息危彩,如果main方法中沒有l(wèi)ooper進行循環(huán)攒磨,那么主線程一運行完畢就會退出,那才不正常了呢!延旧?

所以熙含,ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán)拼坎,那么你的應(yīng)用也就退出了。

那么問題重新問一遍:那為什么這個死循環(huán)不會造成ANR異常呢完疫?

其實原因是顯然的泰鸡,我們知道Android是由事件驅(qū)動的,Looper.loop() 不斷地接收事件壳鹤、處理事件盛龄,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了芳誓,應(yīng)用也就停止了余舶。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞消息锹淌。換言之匿值,消息隊列為空的時候會阻塞主線程,而處理消息的時候不可以阻塞赂摆,這時候的阻塞 5 s就會 ANR挟憔。

也就說我們的代碼其實就是在這個循環(huán)里面去執(zhí)行的,當(dāng)然不會阻塞了烟号。

而且主線程Looper從消息隊列讀取消息曲楚,當(dāng)讀完所有消息時,主線程阻塞褥符。子線程往消息隊列發(fā)送消息龙誊,并且往管道文件寫數(shù)據(jù),主線程即被喚醒喷楣,從管道文件讀取數(shù)據(jù)趟大,主線程被喚醒只是為了讀取消息鹤树,當(dāng)消息讀取完畢,再次睡眠逊朽。因此loop的循環(huán)并不會對CPU性能有過多的消耗罕伯。

總結(jié):Looer.loop()方法可能會引起主線程的阻塞,但只要它的消息循環(huán)沒有被阻塞叽讳,能一直處理事件就不會產(chǎn)生ANR異常追他。

Handler 機制 很多細(xì)節(jié)需要關(guān)注:如線程如何建立和退出消息循環(huán)等等)

這里就是讓你回答 Handler 的工作原理。

關(guān)于Handler岛蚤,在任何地方new Handler 都是什么線程下?

這個需要分類討論:

  1. 像最開始那樣呢邑狸,直接new 出來,不帶Looper 參數(shù)涤妒,那么就在創(chuàng)建 Looper 的線程下单雾。
    public Handler() {
        this(null, false);
    }
    public Handler(Callback callback) {
        this(callback, false);
    }
    public Handler(boolean async) {
        this(null, async);
    }
    public Handler(Callback callback, boolean async) {
        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;
    }

  1. 在任何地方 new 出指定線程的 Handler。例如主線程她紫,看案例:
new Thread(new Runnable() {
        @Override
        public void run() {
                // 在子線程中實例化Handler同樣是可以的硅堆,只要在構(gòu)造函數(shù)的參數(shù)中傳入主線程的Looper即可
                Handler handler = new Handler(Looper.getMainLooper());
        }
}).start();

前面講過,UI線程調(diào)用的 prepare 函數(shù)不一樣贿讹,多保存了UI線程的 Looper 到 Looper.sMainLooper 中的渐逃。其目的是在任何地方都可以實例化 UI 線程的 Handler。

而這時候調(diào)用的構(gòu)造方法是

    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

其實就是與傳入的 Looper綁定民褂,換言之朴乖,如果我傳入的是其他線程的Looper,我同樣也可以實例化其他線程的 Handler助赞。

請解釋下在單線程模型中Message、Handler袁勺、Message Queue雹食、Looper之間的關(guān)系

講到Handler,肯定離不開Looper期丰、MessageQueue群叶、Message這三者和Handler之間的關(guān)系,下面簡略地帶過街立,詳細(xì)自己可以參看上面源碼

  1. Handler
    將要執(zhí)行的Message或者Runnable到消息隊列埠通。

  2. Looper
    每一個線程只有一個Looper,每個線程在初始化Looper之后端辱,然后Looper會維護好該線程的消息隊列虽画,用來存放Handler發(fā)送的Message荣病,并處理消息隊列出隊的Message码撰。它的特點是它跟它的線程是綁定的个盆,處理消息也是在Looper所在的線程去處理颊亮,所以當(dāng)我們在主線程創(chuàng)建Handler時编兄,它就會跟主線程唯一的Looper綁定狠鸳,從而我們使用Handler在子線程發(fā)消息時,最終也是在主線程處理卸察,達(dá)到了異步的效果坑质。

那么就會有人問涡扼,為什么我們使用Handler的時候從來都不需要創(chuàng)建Looper呢吃沪?這是因為在主線程中票彪,ActivityThread默認(rèn)會把Looper初始化好降铸,prepare以后摇零,當(dāng)前線程就會變成一個Looper線程。

  1. MessageQueue
    MessageQueue是一個消息隊列终佛,用來存放Handler發(fā)送的消息铃彰。每個線程最多只有一個MessageQueue牙捉。MessageQueue通常都是由Looper來管理邪铲,而主線程創(chuàng)建時带到,會創(chuàng)建一個默認(rèn)的Looper對象揽惹,而Looper對象的創(chuàng)建,將自動創(chuàng)建一個MessageQueue狭握。其他非主線程论颅,不會自動創(chuàng)建Looper恃疯。

  2. Message
    消息對象今妄,就是MessageQueue里面存放的對象,一個MessageQueu可以包括多個Message杆兵。當(dāng)我們需要發(fā)送一個Message時琐脏,我們一般不建議使用new Message()的形式來創(chuàng)建日裙,更推薦使用Message.obtain()來獲取Message實例昂拂,因為在Message類里面定義了一個消息池格侯,當(dāng)消息池里存在未使用的消息時联四,便返回朝墩,如果沒有未使用的消息收苏,則通過new的方式創(chuàng)建返回倒戏,所以使用Message.obtain()的方式來獲取實例可以大大減少當(dāng)有大量Message對象而產(chǎn)生的垃圾回收問題杜跷。

Handler機制葛闷,Handler除了線程通信還有什么作用

Handler的主要用途 :

  • 推送未來某個時間點將要執(zhí)行的Message或者Runnable到消息隊列阳仔。
  • 在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列中去近范。

1. 推送未來某個時間點將要執(zhí)行的Message或者Runnable到消息隊列

實例:通過Handler配合Message或者Runnable實現(xiàn)倒計時


  • 方法一评矩,通過Handler + Message的方式實現(xiàn)倒計時斥杜。代碼如下:
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;

    private Handler mHandler ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //設(shè)置監(jiān)聽事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //通過Handler + Message的方式實現(xiàn)倒計時
                for (int i = 1; i <= 10; i++) {
                    Message message = Message.obtain(mHandler);
                    message.what = 10 - i;
                    mHandler.sendMessageDelayed(message, 1000 * i); //通過延遲發(fā)送消息忘渔,每隔一秒發(fā)送一條消息
                }
            }
        });

        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                mBinding.time.setText(msg.what + "");   //在handleMessage中處理消息隊列中的消息
            }
        };
    }
}

這里用到了DataBiding畦粮,可能沒用過的同學(xué)看起來有點奇怪,但其實反而簡略了代碼拉背,有一定基礎(chǔ)的同學(xué)看起來都不會有太大壓力椅棺。通過這個小程序两疚,筆者希望大家可以了解到Handler的一個作用就是诱渤,在主線程中勺美,可以通過Handler來處理一些有順序的操作赡茸,讓它們在固定的時間點被執(zhí)行占卧。

  • 方法二华蜒,通過Handler + Runnable的方式實現(xiàn)倒計時。代碼如下:
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //設(shè)置監(jiān)聽事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for (int i = 1; i <= 10; i++) {
                    final int fadedSecond = i;
                    //每延遲一秒域滥,發(fā)送一個Runnable對象
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mBinding.time.setText((10 - fadedSecond) + "");
                        }
                    }, 1000 * i);
                }
            }
        });
    }
}

方法二也是通過代碼讓大家加深Handler處理有序事件的用途启绰,之所以分開Runnable和Message兩種方法來實現(xiàn),是因為很多人都搞不清楚為什么Handler可以推送Runnable和Message兩種對象着倾。

其實卡者,無論Handler將Runnable還是Message加入MessageQueue崇决,最終都只是將Message加入到MessageQueue恒傻。Handler的post Runnable對象這個方法只是對post Message進行了一層封裝,即將Runnable 放到的Message 中的 mCallback 存儲起來沸手,值得注意的是罐氨,如果直接將Message 加入MessageQueue的話栅隐,那么mCallback將為null租悄,所以最終我們都是通過Handler推送了一個Message罷了,至于為什么會分開兩種方法潭辈,只是為了更方便開發(fā)者根據(jù)不同需要進行調(diào)用把敢。下面再來看看Handler的第二個主要用途修赞。

2. 在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列中去

實例:通過Handler + Message來實現(xiàn)子線程加載圖片柏副,在UI線程顯示圖片

效果圖如下



代碼如下(布局代碼也不放出來了)

public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityThreadBinding mBinding = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
        // 設(shè)置點擊事件
        mBinding.clickBtn.setOnClickListener(this);
        mBinding.resetBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            // 響應(yīng)load按鈕
            case R.id.clickBtn:
                // 開啟一個線程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 在Runnable中進行網(wǎng)絡(luò)讀取操作,返回bitmap
                        final Bitmap bitmap = loadPicFromInternet();
                        // 在子線程中實例化Handler同樣是可以的铅歼,只要在構(gòu)造函數(shù)的參數(shù)中傳入主線程的Looper即可
                        Handler handler = new Handler(Looper.getMainLooper());
                        // 通過Handler的post Runnable到UI線程的MessageQueue中去即可
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                // 在MessageQueue出隊該Runnable時進行的操作
                                mBinding.photo.setImageBitmap(bitmap);
                            }
                        });
                    }
                }).start();
                break;
            case R.id.resetBtn:
                mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
                break;
        }
    }

    /***
     * HttpUrlConnection加載圖片,很簡單
     * @return
     */
    public Bitmap loadPicFromInternet() {
        Bitmap bitmap = null;
        int respondCode = 0;
        InputStream is = null;
        try {
            URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10 * 1000);
            connection.setReadTimeout(5 * 1000);
            connection.connect();
            respondCode = connection.getResponseCode();
            if (respondCode == 200) {
                is = connection.getInputStream();
                bitmap = BitmapFactory.decodeStream(is);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(), "訪問失敗", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }
}

很簡單,其實最主要的就是當(dāng)我們需要在主線程執(zhí)行一些操作的時候瓤的,就可以直接使用這種方式圈膏,這種方式有著直接發(fā)送 Message 不可實現(xiàn)的先天優(yōu)勢稽坤。

handler機制組成尿褪,handler機制每一部分的源碼包括looper中的loop方法、threadlocal概念摆马、dispatchmessage方法源碼囤采,runnable封裝message等

上面講解的還記得住么斑唬?

請解釋下在單線程模型中Message、Handler褐着、Message Queue含蓉、Looper之間的關(guān)系

  1. Android的單線程模型

當(dāng)一個程序第一次啟動時馅扣,Android會同時啟動一個對應(yīng)的主線程(Main Thread),主線程主要負(fù)責(zé)處理與UI相關(guān)的事件蓄喇,如:用戶的按鍵事件妆偏,用戶接觸屏幕的事件以及屏幕繪圖事件钱骂,并把相關(guān)的事件分發(fā)到對應(yīng)的組件進行處理。所以主線程通常又被叫做UI線程张吉。

在開發(fā)Android 應(yīng)用時必須遵守單線程模型的原則:Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行肮蛹。

如果在非UI線程中直接操作UI線程省核,會拋出異常气忠,這與普通的java程序不同旧噪。因為 ViewRootImpl 對 UI 操作做了驗證,這個驗證工作是由 ViewRootImpl 的 checkThread 方法來完成的:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

由于UI線程負(fù)責(zé)事件的監(jiān)聽和繪圖米母,因此铁瞒,必須保證UI線程能夠隨時響應(yīng)用戶的需求,UI線程里的操作應(yīng)該向中斷事件那樣短小蜂绎,費時的操作(如網(wǎng)絡(luò)連接)需要另開線程师枣,否則,如果UI線程超過5s沒有響應(yīng)用戶請求陨倡,會彈出對話框提醒用戶終止應(yīng)用程序兴革。順便說一下 ANR 默認(rèn)情況下庶艾,在android中Activity的最長執(zhí)行時間是5秒咱揍,BroadcastReceiver的最長執(zhí)行時間則是10秒。

如果在新開的線程那為什么系統(tǒng)不對 UI 控件的訪問加上鎖機制呢硼砰?缺點有兩個:首先加上鎖機制會讓 UI 訪問的邏輯變得復(fù)雜缅疟;其次鎖機制會降低 UI 訪問的效率存淫,因為鎖機制會阻塞某些線程的執(zhí)行括授。鑒于這兩個缺點荚虚,最簡單且高效的方法就是采用單線程模型來處理UI操作,對于開發(fā)者來說也不是很麻煩渴析,只需要通過 Handler 切換一下 UI 訪問的執(zhí)行線程就好俭茧。
中需要對UI進行設(shè)定,就可能違反單線程模型场斑,因此android采用一種復(fù)雜的Message Queue機制保證線程間通信喧半。

  1. 后面就是分析的那一套了挺据。Message Queue 、 Handler 婉称、 Looper

Handler王暗、Thread和HandlerThread的差別

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread 
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    /**
     * Quits the handler thread's looper.
     * <p>
     * Causes the handler thread's looper to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    /**
     * Quits the handler thread's looper safely.
     * <p>
     * Causes the handler thread's looper to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * Pending delayed messages with due times in the future will not be delivered.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p>
     * If the thread has not been started or has finished (that is if
     * {@link #getLooper} returns null), then false is returned.
     * Otherwise the looper is asked to quit and true is returned.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }


    public int getThreadId() {
        return mTid;
    }
}

由于 HandlerThread 實在是代碼少得變態(tài)庄敛,我就直接將全部源碼貼上來了俗壹。

直接看一下run() 方法可能就明白了,就是將 Looper 啟動起來藻烤,就等于主線程一樣绷雏,可以直接使用 Handler 了怖亭,沒必要 Looper.prepare() 再 Looper.loop() 了涎显。需要知道這根本不是 handler ,而是封裝了 Looper 的 Thread兴猩,方便了子線程與子線程通信棺禾。

還有一點
Handler: 它的消息處理方式是阻塞式的,必須一條一條的處理峭跳。耗時操作 不應(yīng)該用handler處理膘婶。

HandlerThread:繼承自Thread,它有個Looper蛀醉,在這里可以執(zhí)行耗時操作

什么是 IdleHandler悬襟?有什么用?怎么用

https://mp.weixin.qq.com/s/KpeBqIEYeOzt_frANoGuSg
這里還沒寫拯刁,努力更新中...

Handler消息機制脊岳,postDelayed會造成線程阻塞嗎?對內(nèi)存有什么影響?

  1. Handler消息機制
  2. postDelayed會造成線程阻塞嗎:

還有印象么割捅,上面講解的 postDelayed奶躯。postDelayed只是在 post 的時候加了延時,最后這個延時講被轉(zhuǎn)換成執(zhí)行時間存在每一個 Message 中亿驾。而在 loop() 中調(diào)用 next() 的死循環(huán)是阻塞式的嘹黔,只有在下個消息到達(dá)或者有事務(wù)發(fā)生(設(shè)置的nextPollTimeoutMillis到了)時,才通過往pipe管道寫端寫入數(shù)據(jù)來喚醒線程工作莫瞬。

也就是說如果當(dāng)前消息隊列中消息全部為 延時Message(全部沒到執(zhí)行時間)儡蔓,而這個 Message 的執(zhí)行時間又比MessageQueue 中所有消息執(zhí)行時間早,那么在loop循環(huán)取next 的時候就會因為最早這一個Message(剛剛postDelayed的Message)還沒到執(zhí)行時間而阻塞疼邀。

  1. 對內(nèi)存有什么影響


    ThreadLocal內(nèi)存解釋

    Looper 是通過 ThreadLocal 存儲在線程中的喂江,而MessageQueue 是封裝在 Looper 中的。

參考文章:

Android 官方文檔 :Handler
MessageQueue
Looper
《 Android 開發(fā)藝術(shù)探索 》
《 Android 開發(fā)進階 從小工到專家 》
深入理解Android中的Handler機制
一步一步分析Android的Handler機制
【從源碼看Android】03Android MessageQueue消息循環(huán)處理機制(epoll實現(xiàn))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旁振,一起剝皮案震驚了整個濱河市获询,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拐袜,老刑警劉巖吉嚣,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阻肿,居然都是意外死亡瓦戚,警方通過查閱死者的電腦和手機沮尿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門丛塌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人畜疾,你說我怎么就攤上這事赴邻。” “怎么了啡捶?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵姥敛,是天一觀的道長。 經(jīng)常有香客問我瞎暑,道長彤敛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任了赌,我火速辦了婚禮墨榄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勿她。我一直安慰自己袄秩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著之剧,像睡著了一般郭卫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上背稼,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天贰军,我揣著相機與錄音,去河邊找鬼雇庙。 笑死谓形,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疆前。 我是一名探鬼主播寒跳,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼竹椒!你這毒婦竟也來了童太?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胸完,失蹤者是張志新(化名)和其女友劉穎书释,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赊窥,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡爆惧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锨能。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扯再。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖址遇,靈堂內(nèi)的尸體忽然破棺而出熄阻,到底是詐尸還是另有隱情,我是刑警寧澤倔约,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布秃殉,位于F島的核電站,受9級特大地震影響浸剩,放射性物質(zhì)發(fā)生泄漏钾军。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一绢要、第九天 我趴在偏房一處隱蔽的房頂上張望吏恭。 院中可真熱鬧,春花似錦袖扛、人聲如沸砸泛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唇礁。三九已至勾栗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盏筐,已是汗流浹背围俘。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留琢融,地道東北人界牡。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像漾抬,于是被迫代替她去往敵國和親宿亡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

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

  • 1. ANR異常 Application No Response:應(yīng)用程序無響應(yīng)纳令。在主線程中挽荠,是不允許執(zhí)行耗時的操...
    JackChen1024閱讀 1,390評論 0 3
  • 【Android Handler 消息機制】 前言 在Android開發(fā)中,我們都知道不能在主線程中執(zhí)行耗時的任務(wù)...
    Rtia閱讀 4,844評論 1 28
  • 前言 Handler是Android消息機制的上層接口平绩,平時使用起來很方便圈匆,我們可以通過它把一個任務(wù)切換到Hand...
    eagleRock閱讀 1,662評論 0 13
  • 前言: 提到Android消息機制大家應(yīng)該都不陌生,在日常開發(fā)中都不可避免地要涉及到這方面的內(nèi)容捏雌。從開發(fā)的角度來說...
    JiaYang627閱讀 357評論 0 1
  • 01 如果你知道你的未來是什么樣的跃赚,你還有勇氣去面對嗎?《無問西東》《我在未來等你》 02 今天在看書的時...
    嘻哈__閱讀 131評論 0 0