Android Handler 機制詳解(二)源碼解析

在這裏插入圖片描述
ps:這是Handler系列的第二篇文章,讓我來帶大家分析一遍Android Handler機制的源碼,因爲技術有限,歡迎指正與補充

Handler簡介

大家剛開始對於handler的認識就是切換線程更新UI,但如果你分析過Handler的源碼之後,你會發現Handler機制對於整個APP的運行起到了至關重要的作用,而不只是簡簡單單的用於切換線程

基本用法

這個我在第一篇Handler機制中就已經很詳細的說明了,大家可以去我的主頁找到這一篇文章

源碼分析

先來看一下Handler機制中涉及到的幾個核心類:

類名 描述
Message 發送消息的實體,類中用幾個字段,常用的有what、obj,用來存儲信息
MessageQueue 是一種鏈表實現的隊列的數據結構,用來管理Message,先進先出
Looper 通過無限循環來監控MessageQueue,每當有Message發送到隊列時,從中取出Message來處理
Handler 處理消息
ThreadLocal 線程隔離的存儲數據的類,可以在一個線程內存儲數據

先對這幾個類有一個大體的瞭解

消息入隊機制

先從我們熟悉的,Handler.sengMessage(Message msg)方法看起:

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;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
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);
    }

上面三個方法的調用鏈依次爲

sendMessage -> sendMessageAtTime -> sendMessageAtTime

看方法名知其意,這三個方法的作用就是發送消息,方法的參數delayMillis表示消息什麼時候從消息隊列中被取出,時間的單位是毫秒。
可以看到sendMessage內部調用了sendMessageDelayed方法

return sendMessageDelayed(msg, 0);

也是就是通過handleMessage這個方法發送的消息是立即被處理的,延遲時間爲0。
最後調用的是

enqueueMessage(queue, msg, uptimeMillis);

這個方法的作用是將消息入隊,這裏就接觸到了Handler機制中的第一個核心類:MessageQueue,這個方法的作用,正是將發送的Message入隊到MessageQueue中。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;  //註釋1處,標重點
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
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.
                //註釋2處
                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;
    }

注意註釋一處

msg.target = this;  //註釋1處

這個this當然就是我們new的handler實例,也就是說Message持有的handler的引用。劃重點,以後要考。
然後接着看enqueueMessage方法,這個方法的主要邏輯就是鏈表的入隊操作,如果仔細看插入邏輯的話你會發現不是將新的msg插入隊尾,而是插入隊頭

                // New head, wake up the event queue if blocked.
                //註釋2處
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;

注意mMessages這個成員變量,就是鏈表的表頭,瞭解過數據結構的話就會知道,鏈表的表頭就是一個鏈表,因爲通過表頭可以找到鏈表中的所有元素。
到這裏,Handler機制中的消息入隊的部分就分析完畢了。、
有沒有發現忽略的一個非常重要的問題,剛纔說將發送的消息入隊,那這個messagequeue是從哪裏獲取到的?看一下我們寫的代碼,只有一個可能,那就是在new Handle()的時候:

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

		//註釋1處
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
       //註釋2處
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

這樣就出現了Handler機制中的第二個核心類:Looper。這裏的queue正是通過Looper拿到的,然後看一下Looper.myLooper();

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

又出現了第三個核心類,ThreadLoal,這個類的作用就是存儲線程範圍的數據,也就是說,通過ThreadLocal拿到了當前線程的Looper,因爲Handler是在主線程new出來的,所以這裏拿到的是主線程的Looper,然後再通過Looper得到MessageQueue。在這裏可以分析出來,一個線程只有一個Looper,而Looper中有一個MessageQueue,所以一個線程只有一個Looper和一個MessageQueue:
在這裏插入圖片描述
到這裏消息入隊部分差不多就結束了,還有一個問題是:Looper什麼時候設置給主線程的?我們接着往下看

分析

整個流程分析到這裏,僅僅是發送消息,將消息入隊到MessageQueue。並沒有看到有哪裏去調用使用handler是要覆寫的handleMessage方法。到底是哪裏去調用的呢?
前面說到Looper這個類,其實就是通過looper無限循環來不斷詢問MessageQueue是否有新消息插入,如果有的話就取出消息並處理。那是從哪裏去開啓的無限循環呢?很容易就會想到應用程序的入口,ActivityThread的main函數(如果你瞭解過Activity的啓動流程,實際上是通過Zygote進程,使用反射,來調用的這個main函數)

APP能一直運行不退出,其實就是因爲Looper一直在不斷循環。

循環機制

public static void main(String[] args) {
        ...
        
        //註釋1處
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        //註釋2處
        thread.attach(false, startSeq);

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

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //註釋3處
        Looper.loop();
        //註釋4處
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

果不其然看到了熟悉的身影,先看註釋1處

Looper.prepareMainLooper();
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
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));
    }

就是在這裏,APP啓動過程中,就已經給在主線程創建了Looper對象,然後將Looper對象設置給ThreadLocal。然後看一下Looper的構造方法

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

這裏new出了messagequeue對象,剛纔分析入隊機制時,就是通過Looper來給Message賦值,正是因爲Looper初始化時就已經new出了MessageQueue。
從這裏可以看到Looper和messagequeue的關係:Looper包含messagequeue,messagequeue是looper對象的一個成員變量,一個線程只有一個messagequeue。

四個類的關係

分析到這裏,已經可以找到四個類的關係了:

 Looper ---->(引用) MessageQueue ---->(引用) Message ---> (引用)Handler
 //後者都是前者的一個成員變量
 //知道引用關係之後,你可以試着分析,使用Handler爲什麼會存在內存泄漏問題?
 //第三篇,講解內存泄漏時會回答這個問題

到這裏prepareMainLooper()方法就分析完了,這個方法一共做了哪幾件事呢?

  • 創建Looper對象,存入ThreadLocal類中,線程獨有
  • 創建MessageQueue對象,MessagEqueue是Looper的一個成員變量,因此MessagEqueue也是線程獨有

至此,主線程的looper和messageQueue對象創建完畢,那是哪裏開啓循環的呢,當然是下面的註釋3處的loop方法

 //註釋3處
        Looper.loop();
public static void loop() {
        //這個方法之前介紹過,就是得到當前線程的looper對象
        final Looper me = myLooper();
        //如果在子線程創建過handler,而沒有手動開啓循環的話
        //就會報這個異常,原因顯而易見,你沒有在當前線程調用
        //looper.prepare()方法來創建當前線程的looper對象和messageQueue對象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //得到當前線程的MessageQueue對象
        final MessageQueue queue = me.mQueue;

        .
        .
        .

            
        //這裏開啓無限循環
        for (;;) {
            //從消息隊列中拿到下一個Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

           .
           .
           .

            try {
                //註釋1處
                //這裏通過msg的target字段(眼熟嗎)的dispatchMessage方法對msg進行處理
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            }
            //將msg回收
            //這個方法內部就是將msg的每個字段都設置爲null或者默認值
            //然後將這個msg放入緩存池
            //其實緩存池的大小隻有1
            msg.recycleUnchecked();
        }
    }

至此,終於找到開啓無限循環的地方,這個方法裏面有幾處很重要

  • queue.next() ,得到messageQueue的下一個message。
  • msg.target.dispatchMessage(msg); 將message交給handler處理。

其餘的一些重要的地方我寫有註釋
Message的next()方法也很重要,雖然作用很簡單,就是得到下一個消息鏈表中的消息,但是代碼實現沒有這麼簡單,這裏我就不作分析,大家有興趣可以自己下來看 ,我把代碼貼出來,需要注意的一點是,當隊列中沒有消息時,next()方法會堵塞,而不是讓for循環無休止的運行,消耗cpu資源。

@UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 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);
            }

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

消息處理機制

終於分析到消息處理了,在這個方法裏可以看到handleMessage(msg);這個方法了,也就是我們使用Handler時複寫的那個方法。
現在我們來看一下dispatchMessage這個方法

                //註釋1處
                //這裏通過msg的target字段(眼熟嗎)的dispatchMessage方法對msg進行處理
                msg.target.dispatchMessage(msg);
 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            //第一種
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                //第二種
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //第三種
            handleMessage(msg);
        }
    }

這就是我在Handler系列文章中第一篇重點分析的一個方法,處理消息的邏輯。

收尾

最後看main函數的註釋4處

//註釋4處
        throw new RuntimeException("Main thread loop unexpectedly exited");

這裏拋出了異常,也就是說程序不應該運行到這裏。爲什麼呢?想想很簡單呀,已經在上面的loop方法內開啓循環了,程序自然就不會運行到這裏了,如果運行到這裏就說明出問題了,自然要報異常。

思考

Handler機制差不多就分析完了,我分析完之後腦子裏是有一個很大的疑問的,既然是靠主線程中的loop方法無限循環來維持app的運行,那爲什麼Android還能響應點擊事件呢?不是所有邏輯都應該堵塞在循環那裏了嗎?
答案其實很簡單,點擊事件、啓動Activity等等都是將各個系統事件打包成一個Message,發送到MessageQueue中,然後在loop循環內收到這條消息,去做相應的操作。具體深入還要涉及到進程間的通信,這裏就不展開講了


既然都看完了,大家點個贊再走呀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章