從源碼角度淺析Android中的Handler

Handler是Android消息通訊當中最常用的方式之一。
本篇小文將會從Handler的源碼角度去淺析Handler。

總結

因為Handler這個東西其實大家都會用,源碼也多多少少地了解過,所以直接將最關鍵的話,寫到前面,對源碼感興趣的看官可以在看完總結后再往下瀏覽源碼:

  1. 創建Handler
    Handler handler = new Handler();

在主線程當中,可以直接進行Handler的創建。如果是在子線程當中,在創建之前必須先初始化Looper,否則會RuntimeException:

    Looper.prepare();
    Handler handler = new Handler();
    handler.sendEmptyMessage(8);
    Looper.loop();

在初始化Looper的同時,一定要調用Looper.loop()來啟動循環,否則Handler仍然無法正常接收。
并且因為Looper.loop()有死循環的存在,Looper.loop()之后的代碼將無法執行,所以需要將Looper.loop()放在代碼的最后,這點在下面源碼解析會解釋。
并且Looper在每個線程只能存在一個,如果再去手動創建Looper也會拋出RuntimeException。

  1. 發送Handler
    Handler的發送有很多方法,包括發送延時Handler、即時Handler、MessageHandler等等,但是查看這些發送方法的源碼,就會發現這些發送Message的方法最終都會調用MessageQueue.enqueueMessage()方法。
    這個方法其實就是將我們發送的Message入隊到MessageQueue隊列中,這樣,我們的消息就已經發送成功,等待執行了。
  2. 取出Handler
    Looper.prepare()的同時,總會執行looper.loop()語句與之對應。
    查看loop()源碼會發現,這個方法中有一個for(;;)的死循環,會無限執行MessageQueue.next(),而MessageQueue就是我們上一步將Meesage入隊的對象。
    也就是說在創建Looper時,就會啟動MessageQueue的無限遍歷。如果MessageQueue為空,Looper.loop()就會進入休眠,直到再有Message插入到MessageQueue中。
    如果取到Message則會調用message.target.dispatchMessage(),將消息分發給對應的Handler。
  3. 如何在loop()休眠之后喚醒loop()
    在Meesage入隊的時候,也就是執行MessageQueue.enqueueMessage()方法時,enqueueMessage()有一個nativeWeak()的native方法,如果有消息進入,并且Looper是休眠狀態,則會執行該方法喚醒Looper:
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
    nativeWake(mPtr);
}
  1. 整體流程
    先調用Looper.prepare()創建Looper,在創建的同時會自動調用Looper.loop()執行死循環loop()。注意Looper.loop()一定放到代碼的最后一行。
    死循環中會執行MessageQueue.next()方法去取出隊列中的消息,當消息為空時,MessageQueue.next()方法中會執行nativePollOnce()的native方法休眠Looper.loop()死循環。當有新的消息插入到MessageQueue中,也就是調用MessageQueue.enqueueMessage()方法,這個方法當中會判斷Looper是否是休眠狀態,如果是休眠狀態會執行nativeWeak()的native方法來喚醒Looper()。

Handler的使用

  1. 主線程
    主線程當中,Handler可以作為一個成員變量直接進行創建:
    //注意,Handler屬于android.os包
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            System.out.println("主線程的Handler");
        }
    };
    

接著我們試著發送Handler:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //發送一個空Handler,what為888,延遲3秒到達
        handler.sendEmptyMessageDelayed(888,3000);
    }

發送Handler有很多方法:發送空Message、發送延遲的空Message、發送Message、發送延遲的Message等:


接著我們運行項目,就會發現3s后控制臺有Log的打印。

  1. 子線程
    子線程中創建Handler的流程,和在主線程基本一致,只是多了一步而已,但這一步非常關鍵。
    我們先按照主線程的步驟,看看會有什么問題。
    子線程中創建Handler:
new Thread(){
    @Override
    public void run() {
        super.run();
        Handler handler = new Handler(){
             @Override
            public void handleMessage(Message msg) {
                System.out.println("子線程的Handler");
            }
        };
    }
}.start();

接著我們運行項目,會發現項目崩潰了:

java.lang.RuntimeException: Can''t create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at com.my.oo.MainActivity$2$1.<init>(MainActivity.java:0)

很明顯,錯誤信息說的是在當前線程中創建Handler失敗因為沒有執行Looper.prepare()
那我們按照錯誤原因,在創建Handler之前,加上Looper.prepare():

//子線程
new Thread(){
    @Override
    public void run() {
        super.run();
        //添加Looper.prepare();
        Looper.prepare();
        //之后再創建Handler
        Handler handler = new Handler(){
             @Override
            public void handleMessage(Message msg) {
                System.out.println("子線程的Handler");
            }
        };
        //發送消息
        handler.sendEmptyMessage(111);
    }
}.start();

這次再運行項目,我們就會發現項目正常運行沒有問題。但是發送Handler仍然無法接收,那是因為我們沒有啟動Looper的遍歷:

//子線程
new Thread(){
    @Override
    public void run() {
        super.run();
        //添加Looper.prepare();
        Looper.prepare();
        //之后再創建Handler
        Handler handler = new Handler(){
             @Override
            public void handleMessage(Message msg) {
                System.out.println("子線程的Handler");
            }
        };
        //發送消息
        handler.sendEmptyMessage(111);
        //啟動Looper的遍歷功能
        Looper.loop();
    }
}.start();

這里一定要注意,Looper.loop()必須放到代碼的最后。因為Looper.loop()中有死循環,會導致之后的代碼無法執行。這里可以等到查看主線程創建過程的源碼時證實。

  1. 使用總結
    Handler的使用就是這么簡單,要注意的就是子線程當中使用Handler時,一定要先調用Looper.prepare(),最后調用Looper.loop(),否則項目會崩潰或無法接收Handler。至于為什么會這樣,我們在源碼里面找原因。

源碼解析

  1. Handler創建
    我們先看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;
}

我們可以看到,Handler的構造方法當中是去獲取了一個叫做Looper的類對象,如果該對象為空,就會拋出剛才我們上面發生的異常。所以我們需要在創建Handler之前,一定要先執行Looper.prepare()
那么問題來了,為什么主線程就不需要執行Looper.prepare()就可以直接創建Handler呢?
我們可以隨意根據代碼猜測一下:
這里Handler的構造方法的代碼已經很明顯了,Looper類是必要的,那么主線程可以成功創建Handler,是不是就代表著主線程的Looper不為空呢?


是不是主線程在初始化的時候Looper也跟著初始化了呢!?帶著看破一切(瞎猜)的思路,我們來看主線程的初始化源碼。
經過了長時間的Google,我們知道了主線程類叫做:ActivityThread
該類當中有main方法:

public static void main(String[] args) {
    //....省略部分代碼
    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"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

我們很自然(驚奇)地發現,在主線程創建的過程中,果然(真的)有與Looper類相關的內容。
這里有重點(敲黑板):之前說的Looper.loop()后的代碼不會執行,這里得到了證實:

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

下面異常的意思是:主線程的Looper意外退出。
也就是當Looper.loop()執行失敗的意思,但是當Looper.loop()執行成功時,是不會執行下面的代碼的!因為Looper.loop()必須放到方法的最后,否則會導致后面的代碼無法執行
好的,接著往下看,點進prepareMainLooper()會發現,其實內部就是調用了prepare()

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

所以到現在,我們解決了我們的第一個問題:為什么主線程當中不需要執行Looper.prepare()
接著,我們去瀏覽Looper.prepare()

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //new Looper()并設置給當前線程
    sThreadLocal.set(new Looper(quitAllowed));
}

沒什么東西,前面對線程當中的Looper進行了判空,如果不為空則會拋出RuntimeEception
這也就是說,每個線程當中只能有一個Looper,當你嘗試去創建第二時,就會發生異常,所以Looper.prepare()每個線程中只能調用一次。
后面則new了Looper并且設置給當前線程。
new Looper()中初始化了MessageQueue

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

到這里,Handler的創建就完成了!

  1. Handler發送Meesage
    Handler的發送方法有很多,包括發送延時Handler、及時Handler、空Hanlder等等。
    查看源碼會發現,所有發送方法最后調用的都是同一個方法:MessageQueueenqueueMessage()
    有的看官就會問:Handler中怎么會有MeesageQueue
    這個在上面new Looper()的源碼中已經體現了:
    new Handler()new Looper()
    new Looper()new MessageQueue()
    所以其實初始化Handler的同時,LooperMeesageQueue都已經初始化完成了。
    下面我們來看消息入隊方法MessageQueue.enqueueMessage()的全部源碼:
boolean enqueueMessage(Message msg, long when) {
        //Meesage是否可用
        //這里的msg.target指的就是發送該Message的Handler
        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) {
            //判斷是否調用了quit()方法,即取消信息
            //如果調用了,則其實Handler的Looper已經銷毀,無法發送消息
            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;
            }

            //將消息添加到MessageQueue的具體操作
            //每來一個新的消息,就會按照延遲時間的先后重新進行排序
            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 {
                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;
            }

            //如果Looper.loop()是休眠狀態
            //則調用native方法喚醒loop()
            //---重點---Looper的喚醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

將Message入隊到MeesageQueue的核心代碼,就是這些。
根據注釋也基本能理解該方法的作用。

  1. 取出Message
    取出Meesage想必大家都知道在哪里取出:Looper.loop()
public static void loop() {
    //Looper的判空
    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();
    //取出Message,死循環
    for (;;) {
        //取出Meesage的核心代碼
        //在當next()返回為空時,next()中會休眠loop()
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        msg.recycleUnchecked();
    }
}

Looper.loop()的核心就是取出Message,而取出Message的核心就是MeesageQueue.next()

 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();
        }
        //喚醒Looper.loop()的native方法
        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 
                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 
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.M
                } 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 hand
            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(pendingIdleHandlerCo
            }
            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;
    }
}

取出Meesage的代碼有些多,大部分都是一些優化邏輯:next() 方法還做了其他一些事情,這些其它事情是為了提高系統效果,利用消息隊列在空閑時通過 idle handler 做一些事情,比如 gc 等等。

結語

到這里Handler源碼的淺析就結束了,總結在最上方,建議各位看官再去看一下總結加深印象。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容