Android進階:3、這一次,咱們用最詳細的方式解析Android消息機制的源碼

決定再寫一次有關Handler的源碼

Handler源碼解析

1、建立Handler對象

使用handler最簡單的方式:直接new一個Handler的對象性能優化

Handler handler = new Handler();複製代碼

因此咱們來看看它的構造函數的源碼:
bash

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

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
    }複製代碼

這段代碼作了三件事:
架構

一、校驗是否可能內存泄漏
二、初始化一個Looper mLooper
三、初始化一個MessageQueue mQueue

咱們一件事一件事的看:
less

一、校驗是否存在內存泄漏

Handler的構造函數中首先判斷了FIND_POTENTIAL_LEAKS的值,爲true時,會獲取該對象的運行時類,若是是匿名類,成員類,局部類的時候判斷修飾符是否爲static,不是則提示可能會形成內存泄漏。
問:爲何匿名類,成員類,局部類的修飾符不是static的時候可能會致使內存泄漏呢?異步

答:由於,匿名類,成員類,局部類都是內部類,內部類持有外部類的引用,若是Activity銷燬了,而Hanlder的任務尚未完成,那麼Handler就會持有activity的引用,致使activity沒法回收,則致使內存泄漏;靜態內部類是外部類的一個靜態成員,它不持有內部類的引用,故不會形成內存泄漏async

這裏咱們能夠思考爲何非靜態類持有外部類的引用?爲何靜態類不持有外部類的引用?

問:使用Handler如何避免內存泄漏呢?
答:使用靜態內部類的方式ide

二、初始化初始化一個Looper mLooper

這裏得到一個mLooper,若是爲空則跑出異常:函數

"Can't create handler inside thread that has not called Looper.prepare() "複製代碼

若是沒有調用Looper.prepare()則不能再線程裏建立handler!咱們都知道,若是咱們在UI線程建立handler,是不須要調用這個方法的,可是若是在其餘線程建立handler的時候,則須要調用這個方法。那這個方法到底作了什麼呢?咱們去看看代碼:
oop

public static void prepare() {
        prepare(true);
    }

    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));
    }複製代碼

先取sThreadLocal.get()的值,結果判斷不爲空,則跑出異常「一個線程裏只能建立一個Looper」,因此sThreadLocal裏存的是Looper;若是結果爲空,則建立一個Looper。那咱們再看看,myLooper()這個方法的代碼:
性能

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }複製代碼

總上咱們得出一個結論:當咱們在UI線程建立Handler的時候,sThreadLocal裏已經存了一個Looper對象,因此有個疑問:
當咱們在UI線程中建立Handler的時候sThreadLocal裏的Looper從哪裏來的?
咱們知道,咱們獲取主線程的Looper須要調用getMainLooper()方法,代碼以下:

public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }複製代碼

因此咱們跟蹤一下這個變量的賦值,發如今方法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();
        }
    }複製代碼

  • 第一步調用了prepare(false),這個方法咱們剛纔已經看了,是建立一個Looper對象,而後存到sThreadLocal中;\
  • 而後判斷sMainLooper是否爲空,空則拋出異常
  • sMainLooper不爲空,則sMainLooper = myLooper()

至此sMainLooper對象賦值成功,因此,咱們須要知道prepareMainLooper()這個方法在哪調用的,跟一下代碼,就發如今ActivityThread的main方法中調用了Looper.prepareMainLooper();。如今真相大白:
當咱們在UI線程中建立Handler的時候sThreadLocal裏的Looper是在ActivityThread的main函數中調用了prepareMainLooper()方法時初始化的
ActivityThread是一個在一個應用進程中負責管理Android主線程的執行,包括活動,廣播,和其餘操做的類

三、初始化一個MessageQueue mQueue

從代碼裏咱們看出這裏直接調用了:mLooper.mQueue來獲取這個對象,那這個對象可能在Looper初始化的時候就產生了。咱們去看看Looper的初始化代碼:

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

代碼很簡單,就是建立了MessageQueue的對象,並得到了當前的線程。

至此,Handler的建立已經完成了,本質上就是得到一個Looper對象和一個MessageQueue對象!

2、使用Handler發送消息

Handler的發送消息的方式有不少,咱們跟蹤一個方法sendMessage方法一直下去,發現最後居然調用了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);
    }複製代碼

這段代碼作了幾件事

一、給msg.target賦值,也就是Handler對象
二、給消息設置是不是異步消息。
三、調用MessageQueue 的enqueueMessage(msg, uptimeMillis)方法
咱們只關注第三步:這一步把Handler的發送消息轉給了MessageQueue的添加消息的方法。
因此至此,Handler發送消息的任務也已經完成了, 本質上就是調用MessageQueue本身的添加消息的方法!

3、MessageQueue添加消息

MessageQueue的構造函數代碼以下:

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }複製代碼

也沒作什麼特別的事情。咱們去看看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.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }複製代碼

代碼很長,可是經過觀察這段代碼咱們發現這個MessageQueue其實是個鏈表,添加消息的過程其實是一個單鏈表的插入過程。

因此咱們知道了Handler發送消息的本質實際上是把消息添加到MessageQueue中,而MessageQueue實際上是一個單鏈表,添加消息的本質是單鏈表的插入

4、從消息隊列裏取出消息

咱們已經知道消息如何存儲的了,咱們還須要知道消息是如何取出的。
因此咱們要看一下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;


        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }


            try {
                msg.target.dispatchMessage(msg);
            }

        }
    }複製代碼

代碼太長我刪了部分代碼。能夠看出這個方法主要的功能是很簡單的。

  • 獲取Looper對象,若是爲空,拋異常。
  • 獲取消息隊列MessageQueue queue
  • 遍歷循環從消息隊列裏取出消息,當消息爲空時,循環結束,消息不爲空時,分發出去!

可是實際上當沒有消息的時候queue.next()方法會被阻塞,並標記mBlocked爲true,並不會馬上返回null。而這個方法阻塞的緣由是nativePollOnce(ptr, nextPollTimeoutMillis);方法阻塞。阻塞就是爲了等待有消息的到來。那若是在有消息加入隊列,loop()方法是如何繼續取消息呢?
這得看消息加入隊列的時候有什麼操做,咱們去看剛纔的enqueueMessage(msg, uptimeMillis)方法,發現

if (needWake) {
    nativeWake(mPtr);
}複製代碼

當needWake的時候會調用一個本地方法喚醒讀取消息。
因此這裏看一下消息分發出去以後作了什麼?

msg.target.dispatchMessage(msg);複製代碼

上面講過這個target其實就是個handler。因此咱們取handler裏面看一下這個方法代碼

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

代碼很是簡單,當callback不爲空的時候調用callback的handleMessage(msg)方法,當callback爲空的時候調用本身的handleMessage(msg)。通常狀況下咱們不會傳入callback,而是直接複寫Handler的handleMessage(msg)方法來處理咱們的消息。

喜歡個人文章的話能夠點個讚的哦

寫在最後

不少人在剛接觸這個行業的時候或者是在遇到瓶頸期的時候,總會遇到一些問題,好比學了一段時間感受沒有方向感,不知道該從那裏入手去學習,對此我整理了一些資料,須要的能夠免費分享給你們,後面也會整理比較新比較火的技術分享的flutter—性能優化—移動架構—資深UI工程師 —NDK

若是喜歡個人文章,想與一羣資深開發者一塊兒交流學習的話,歡迎加入個人合做羣Android Senior Engineer技術交流羣:925019412

領取方式:Android技術交流羣925019412

相關文章
相關標籤/搜索