Handler機制實現原理(四)handler的源碼分析

Handler自己可在多線程之間調用,無論它在哪一個線程發送消息,都會回到它被初始化的哪一個線程中接收到消息。bash

初始化

Handler有7個構造方法,分別對應不一樣的參數來初始化不一樣的Handler屬性,可是真正完成初始化操做的只有兩個構造方法:多線程

// 是否須要查找潛在的漏洞
    private static final boolean FIND_POTENTIAL_LEAKS = false;

    /**
     * 將Callback接口做爲構造方法參數,能夠用做接收消息的回調
     * 這樣就能夠省去本身重寫Handler自身的handleMessage方法
     *
     * @param msg 接收到的消息
     * @return 是否須要進一步處理,即調用Handler自身的handleMessage方法
     */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;

    // 發送的消息是否爲異步的,默認是false
    final boolean mAsynchronous;

    /**
     * @hide 隱藏的構造方法,外部不可見
     */
    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());
            }
        }

        // 獲得當前線程的Looper
        mLooper = Looper.myLooper();

        // 若是Looper還沒初始化拋出異常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

        // 獲得當前線程的MessageQueue
        mQueue = mLooper.mQueue;

        
        mCallback = callback;
        mAsynchronous = async;
    }

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

複製代碼

從上面代碼的得知,構造方法初始化的工做就是給mLooper,mQueue,mCallbackmAsynchronous這幾個關鍵的屬性賦值.mLoopermQueue天然就是當前線程下的Looper和MessageQueue了,若是傳遞了Looper參數就直接賦值,若是沒傳遞就調用Looper.myLooper();獲得當前線程的Looper。異步

mCallback是Handler內部定義的一個簡單接口,其目的是爲了替代傳統的接收消息方法。固然使用mCallback的同時並不會影響正常的Handler消息分發。此處解釋從後面接收消息時的邏輯就能夠看到。async

mAsynchronous的意思是該Handler發送的消息是不是異步的,從前面Message源碼的文章中咱們知道Message中有一個設置消息是否爲異步消息的方法,MessageQueue對異步消息的處理也與同步消息不一樣。此處若是設置了mAsynchronoustrue,那麼這個Handler發送的全部消息就都是異步消息。ide

在有兩個參數的構造方法中咱們會發現有一段檢查是否存在內存泄漏的代碼,爲何會這樣呢?在分析完發送消息和接收消息後再說這個。oop

固然,除了上面兩個構造方法外還有其它幾個構造方法,但均是調用上面兩個方法:post

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

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

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

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

    /**
     * @hide 隱藏的構造方法,外部不可見
     */
    public Handler(boolean async) {
        this(null, async);
    }

複製代碼

發送消息

使用Handler發送消息時咱們知道它分爲兩類:ui

  • postXXX()方法切換回原線程。
  • sendMessageXXX()方法發送消息到原線程。

其實這兩種方法本質都是發送一個Message對象到原線程,只不過PostXXX()方法是發送了一個只有Runnable callback屬性的Message對象。this

先來看一下sendMessageXXX()類的方法:spa

// 發送一條普通消息
    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }

    // 發送一條空消息
    public final boolean sendEmptyMessage(int what){
        return sendEmptyMessageDelayed(what, 0);
    }

    // 發送一條空的延時消息
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    // 發送一條空的定時消息
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }

    // 發送一個普通的延時消息
    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    // 發送一個普通的定時消息
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //獲得消息隊列
        MessageQueue queue = mQueue;
        // 若是消息隊列是空的記錄日誌而後結束方法
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 執行消息入隊操做
        return enqueueMessage(queue, msg, uptimeMillis);
    }

複製代碼

從上面的代碼中發現,無論調用何種發送消息的方法,最後真正調用的都是sendMessageAtTime()方法。而真正發送的核心方法也就是入隊方法是Handler的enqueueMessage()方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // 將消息的宿主設置爲當前Handler自身
        msg.target = this;

        //若是Handler被設置成了異步就把消息也設置成異步的
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }

        // 執行消息隊列的入隊操做
        return queue.enqueueMessage(msg, uptimeMillis);
    }

複製代碼

看到這裏終於明白了爲啥Looper和MessageQueue一直在使用Message的target屬性而咱們卻歷來沒有給它賦值過,是Handler在發送消息前本身賦值上去的。

看完了發送消息類的方法在看看切換線程類的方法幹了什麼:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    private static Message getPostMessage(Runnable r, Object token) {
        Message m = Message.obtain();
        m.obj = token;
        m.callback = r;
        return m;
    }

    public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    public final boolean postAtTime(Runnable r, long uptimeMillis) {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
    
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
    
    public final boolean postDelayed(Runnable r, long delayMillis){
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    
    public final boolean postAtFrontOfQueue(Runnable r){
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

複製代碼

上本節開頭講的同樣,postXXX()類方法就是構造了一個只有Runnable callback的Message對象,而後走正常發送消息的方法。惟一有一個特例就是postAtFrontOfQueue()方法,它調用了sendMessageAtFrontOfQueue()方法是以前發送消息沒有用到過的:

public final boolean sendMessageAtFrontOfQueue(Message msg) {
        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, 0);
    }

複製代碼

原來這個方法特殊的地方就是在入隊的時候時間參數爲0,咱們在MessageQueue源碼知道若是入隊消息的時間參數爲0那麼這個消息會被直接放在隊列頭。因此,postAtFrontOfQueue()方法就是直接在消息隊列頭部插入了一個消息。

接收消息

在Looper 的源碼中咱們知道每當從MessageQueue中取出一個消息時就會調用這個消息的宿主target中分發消息的方法:

// Looper分發消息
msg.target.dispatchMessage(msg);
複製代碼

而這個宿主target也就是咱們的Handler,全部Handler接收消息就是在這個dispatchMessage()方法中了:

public void dispatchMessage(Message msg) {
        // 若是Message的callback不爲空,說明它是一個經過postXXX()方法發送的消息
        if (msg.callback != null) {
            // 直接運行這個callback
            handleCallback(msg);
        } else {
            //若是mCallback 不爲空說明Handler設置了Callback接口
            // 先執行接口處理消息的方法
            if (mCallback != null) {
                // 若是callback接口處理完消息返回true說明它將消息攔截
                // 再也不執行Handler自身的處理消息方法,直接結束方法
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 調用Handler自身處理消息的方法
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        // 運行callback,也就是這個Runnable接口
        message.callback.run();
    }

    // Handler自身處理消息的方法,開發者須要從新該方法來實現接收消息
    public void handleMessage(Message msg) {
    }

複製代碼

因而可知,若是是單純的PostXXX()方法發送的消息,Handler接收到了以後直接運行Message對象的Runnable接口,不會將它當作一個消息進行處理。

而咱們的mCallback接口是徹底能夠替代Handler自身接收消息的方法,由於其高優先處理等級,它甚至能夠選擇攔截掉Handler自身的接收消息方法。

內存泄漏的可能

咱們在使用Handler的時候寫法通常以下:

private final Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {

        }
    };

複製代碼

因爲重寫了handleMessage()方法至關於生成了一個匿名內部類,也就至關於以下代碼:

private final Handler handler = new MyHandler ();

    class MyHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
            
        }
    }

複製代碼

但是你有沒有想過內部類憑什麼可以調用外部類的屬性和方法呢?答案就是內部類隱式的持有着外部類的引用,編譯器在建立內部類時把外部類的引用傳入了其中,只不過是你看不到而已。

既然Handler做爲內部類持有着外部類(多數狀況爲Activity)的引用,而Handler對應的通常都是耗時操做。當咱們在子線程執行一項耗時操做時,用戶退出程序,Activity須要被銷燬,而Handler還在持有Activity的引用致使沒法回收,就會引起內存泄漏。

解決方法分爲兩步

  1. 生成內部類時把內部類聲明爲靜態的。
  2. 使用弱引用來持有外部類引用。

靜態內部類不會持有外部類的引用,且弱引用不會阻止JVM回收對象。

static class MyHandler extends Handler{

        WeakReference<Activity> mActivity ;

        public MyHandler(Activity activity){
            mActivity = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = mActivity.get();

            if (activity == null){
                return;
            }

            // do something

        }
    }

複製代碼

因此,在文章剛開始初始化方法中檢查漏洞的代碼其實就是檢查這個內存泄漏的可能性:

// 檢查是否存在內存泄漏的可能
        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());
            }
        }
複製代碼

大功告成,完美!

相關文章
相關標籤/搜索