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

Handler本身可在多線程之間調用,不管它在哪個線程發送消息,都會回到它被初始化的哪個線程中接收到消息。

初始化

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消息分發。此處解釋從后面接收消息時的邏輯就可以看到。

mAsynchronous的意思是該Handler發送的消息是否是異步的,從前面Message源碼的文章中我們知道Message中有一個設置消息是否為異步消息的方法,MessageQueue對異步消息的處理也與同步消息不同。此處如果設置了mAsynchronoustrue,那么這個Handler發送的所有消息就都是異步消息。

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

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

   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發送消息時我們知道它分為兩類:

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

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

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

    // 發送一條普通消息
    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());
            }
        }

大功告成,完美!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容