Handler知識收集整理

我是代碼搬運工,不能僅僅只是搬運,還要整理一下。android

1. Handler組成部分:

  1. Message:消息
  2. Handler:消息的發起者
  3. Looper:消息的遍歷者
  4. MessageQueue:消息隊列

Handler流程圖

2. Handler的使用流程:

使用Handler以前的準備工做有三步:面試

  1. 調用Looper.prepare()(主線程不須要調這個,由於APP建立時,main方法裏面已經幫咱們建立了)設計模式

  2. 建立Handler對象,重寫handleMessage方法(你能夠不重寫),用於處理message回調的bash

  3. 調用Looper.loop()app

其中:異步

Looper.prepare()的做用主要有如下三點:async

  1. 建立Looper對象ide

  2. 建立MessageQueue對象,並讓Looper對象持有oop

  3. 讓Looper對象持有當前線程源碼分析

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

    private static void prepare(boolean quitAllowed) {
        // 規定了一個線程只有一個Looper,也就是一個線程只能調用一次Looper.prepare()
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 若是當前線程沒有Looper,那麼就建立一個,存到sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }
            
    
複製代碼

從上面的代碼能夠看出,一個線程最多隻有一個Looper對象。當沒有Looper對象時,去建立一個Looper,並存放到sThreadLocal中

private Looper(boolean quitAllowed) {
        // 建立了MessageQueue,並供Looper持有
        mQueue = new MessageQueue(quitAllowed);
        // 讓Looper持有當前線程對象
        mThread = Thread.currentThread();
    }
    
複製代碼

這裏主要就是建立了消息隊列MessageQueue,並讓它供Looper持有,由於一個線程最多隻有一個Looper對象,因此一個線程最多也只有一個消息隊列。而後再把當前線程賦值給mThread。

Handler使用流程:

  1. Handler.post(或sendMessage): handler發送消息msg

  2. MessageQueue.enqueueMessage(msg, uptimeMillis):msg加入message隊列

  3. loop() 方法中從MessageQue中取出msg,而後回調handler的dispatchMessage,而後執行callback(若是有的話) 或 handleMessage。(注意,loop方法是一直在循環的,從前面的handler準備工做開始就已經一直在運行了)

如圖所示:

ThreadLocal

3. Handler具體源碼:

3.1. Message獲取

Message的獲取方式有兩種:

1. Message msg = new Message();

2. Message msg = Message.obtain();

從全局池返回一個新的消息實例。容許咱們在許多狀況下避免分配新對象。

    /**
     * Return a new Message instance from the global pool.      Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

複用以前用過的 Message 對象,這裏其實是用到了一種享元設計模式,這種設計模式最大的特色就是複用對象,避免重複建立致使的內存浪費

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
    
每次message使用完了以後會調用recycleUnchecked回收message,方便下次使用

複製代碼

Message核心的信息有這些:

public int what;

    public int arg1;

    public int arg2;

    public Object obj;

    /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;

複製代碼
3.2 Handler的發送消息

handler提供的發送消息的方法有不少,大體分爲兩類:

  1. Handler.post(xxx);
  2. Handler.sendMessage(xxx);

雖然方法不少,但最終都會回調到這個方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
複製代碼

這個方法就是將消息添加到隊列裏。

3.3 MessageQueue的添加消息

enqueueMessage(添加消息)

源碼分析以下:

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

			// 標記這個 Message 已經被使用
            msg.markInUse();
            msg.when = when;
            // 這是這個消息隊列裏的目前第一條待處理的消息(當前消息隊列的頭部,有可能爲空)
            Message p = mMessages;
            boolean needWake;
            // 若是目前隊列裏沒有消息 或 這條消息msg須要當即執行 或 這條消息msg的延遲時間比隊列裏的第一條待處理的消息還要早的話,走這個邏輯
            if (p == null || when == 0 || when < p.when) {
            	 // 把消息插入到消息隊列的頭部
                // 最新的消息,若是已經blocked了,須要喚醒
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                
                Message prev;
                
                // 下面這個for循環的做用就是找出msg應該放置的正確位置
                // 通過下面這個for循環,最終會找出msg的前一個消息是prev,後一個消息是p
                
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 上面的for循環得出的結果就是:msg應該在prev後面,在p前面
                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;
    }

複製代碼

上面就是handler發送消息過來,而後添加到消息隊列裏。

下面就開始講添加消息到隊列以後的事情:取消息,而後執行。

3.4 Loop的取消息

前面已經說到,在使用Handler以前有三步準備工做:

  1. 調用Looper.prepare()(主線程不須要調這個,由於APP建立時,main方法裏面已經幫咱們建立了)

  2. 建立Handler對象,重寫handleMessage方法(你能夠不重寫),用於處理message回調的

  3. 調用Looper.loop()

其中第三步的Looper.loop()的做用就是不斷的從MessageQueue隊列裏取消息,也就是說,在使用handler發消息以前,就已經開始了loop的循環了

loop()源碼比較長,這裏摘取核心部分:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     *
     * 大概的翻譯就是:在這個線程中運行消息隊列。確保調用{@link #quit()}來結束循環。
     *
     */
    public static void loop() {
    
    	····
        ····

        for (;;) {
        	// 不斷的從MessageQueue的next方法裏取出隊列的頭部消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ·····
            ·····
            ·····
           
            try {
            	// msg.target是Message在建立時傳入的Handler,也就是發送這條消息的發送者handler
            	// 因此最終會回調到handler的dispatchMessage方法
            	
                msg.target.dispatchMessage(msg);
                
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
           
           ····
           ····
			
			// 回收msg,重複利用
            msg.recycleUnchecked();
        }
    }

複製代碼

loop()的做用就是不斷的從MessageQueue裏取消息,而後回調到dispatchMessage裏,再看看dispatchMessage裏幹啥了

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

複製代碼

因此最終會執行callback或handleMessage。

4. 面試常見問題

  1. Android中,有哪些是基於Handler來實現通訊的?

答:App的運行、更新UI、AsyncTask、Glide、RxJava等

  1. 處理Handler消息,是在哪一個線程?必定是建立Handler的線程麼?

答:建立Handler所使用的Looper所在的線程

  1. 消息是如何插入到MessageQueue中的?

答: 是根據when在MessageQueue中升序排序的,when=開機到如今的毫秒數+延時毫秒數

  1. 當MessageQueue沒有消息時,它的next方法是阻塞的,會致使App ANR麼?

答:不會致使App的ANR,是Linux的pipe機制保證的,阻塞時,線程掛起;須要時,喚醒線程

  1. 子線程中可使用Toast麼?

答:可使用,可是Toast的顯示是基於Handler實現的,因此須要先建立Looper,而後調用Looper.loop。

  1. Looper.loop()是死循環,能夠中止麼?

答:能夠中止,Looper提供了quit和quitSafely方法

  1. Handler內存泄露怎麼解決?

答: 靜態內部類+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息

下面逐個來看看這些問題:

4.1 Android中常見的Handler使用
  1. 保證App的運行

App的入口實際上是ActivityThread的main方法:

public static void main(String[] args) {
        // 建立主線程中的Looper
        Looper.prepareMainLooper();
        
        // 建立ActivityThread對象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            // 建立用於通訊的Handler
            sMainThreadHandler = thread.getHandler();
        }
        // 執行loop方法
        Looper.loop();
    }
    
複製代碼

能夠看到這個main方法中,主要有以下幾步:

  1. 建立了主線程中的Looper,建立Looper時,會建立一個MessageQueue用於存放消息。
  2. 建立了ActivityThread對象,並調用了它的attach方法,這個方法就是去建立Application、調用Application的onCreate方法以及告訴ActivityManagerService如今App啓動了。
  3. 建立了用於通訊的Handler,它是一個H對象。
  4. 調用Looper.loop方法,開始循環從主線程中的MessageQueue中取出消息來處理。

回顧下,Handler機制的原理圖:

Handler流程圖

能夠知道,App啓動後,由於Looper.loop是一個死循環,致使main方法一直沒有執行完,也就是說,咱們後續App中的全部操做,都是發生在Looper.loop中的

那Handler機制,是怎麼保證App的運行的呢?咱們來看看ActivityThread中用於通訊的Handler的定義:

private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        public static final int PAUSE_ACTIVITY_FINISHING= 102;
        public static final int STOP_ACTIVITY_SHOW      = 103;
        public static final int STOP_ACTIVITY_HIDE      = 104;
        // 省略部分代碼

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
                    case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
                    // 省略部分代碼
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    handleRelaunchActivity(r);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&2) != 0);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case PAUSE_ACTIVITY_FINISHING:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&1) != 0);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_SHOW:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_HIDE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                 // 省略部分代碼
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }


複製代碼

經過代碼能夠看到,H繼承於Handler,定義了不少消息的類型,好比啓動Activity、中止Activity、顯示Window、電量太低等,它重寫了handleMessage方法,用於處理這些消息。

那發消息是在哪裏發的呢?咱們以鎖屏調用Activity的onStop生命週期爲例,其實就是在ApplicationThread中,它是一個ActivityThread的內部類咱們拿啓動Activity這個消息舉例,ActivityManagerService會先經過binder調用ApplicationThread中的scheduleStopActivity方法(牽涉到進程間通訊,不懂能夠略過),這個方法是在咱們App的Bindler線程池中執行的,那看看它是怎麼切換到主線程去啓動Activity的。

public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

複製代碼

咱們看到,這裏就是調用sendMessage方法,第一個參數,不就是上面H中定義的消息類型麼?接着看 sendMessage方法,它最後會調用到以下這個多參的構造方法:

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

複製代碼

能夠看到,就是使用mH發送一個消息,而mH就是ActivityThread中定義的類型爲H的成員變量,定義以下:

final H mH = new H();
 
複製代碼

因此,調用鎖屏時,調用Activity的onStop方法,流程以下:

  1. ActivityManagerService經過binder方式,調用ApplicationThread的scheduleStopActivity方法
  2. ApplicationThread的scheduleStopActivity方法,經過H把消息加入到主線線程的MessageQueue中
  3. 主線程的Looper遍歷MessageQueue,取到該消息時,調用H的handleMessage方法進行處理
  4. 在H的handleMessage中,調用Activity的onStop方法

流程圖以下:

鎖屏

那麼,咱們App運行原理就出來了,App的運行依賴於Handler機制,當主線程當MessageQueue有消息時,主線程的Looper.loop會不斷從主線程中的MessageQueue中取出消息來處理(好比Activity的onCreate其實就是屬於對MessageQueue中取出的一個消息的處理),這樣就保證了App運行

有消息時

當MessageQueue沒有消息時,MessageQueue的next方法會阻賽,致使當前線程掛起,等有消息(通常爲系統進程經過binder調用App的ApplicationThread中的方法,注意,方法在binder線程池中執行,而後ApplicationThread使用ActivityThread中的H對象發送消息,加入消息到主線程的MessageQueue中,當發現主線程被掛起了,則會喚醒主線程)

因此,當沒有任何消息時,咱們的App的主線程,是屬於掛起的狀態。有消息來時(鎖屏、點擊等),主線程會被喚醒,因此說,Handler機制保證了App的運行。

沒消息時

4.2 Handler更新UI

咱們知道,若是在子線程直接更新UI會拋出異常,異常以下:

子線程更新UI拋異常

咱們可使用Handler在子線程中更新UI,經常使用的方式有以下幾種:

  1. Handler的sendMessage方式
  2. Handler的post方式
  3. Activity的runOnUiThread方法
  4. View的post方式

2.1 Handler的sendMessage方式

final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("handler.sendMessage方式");
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 使用sendMessage方式
                Message msg = Message.obtain();
                msg.what = 100;
                handler.sendMessage(msg);
            }
        }).start();
複製代碼

這種方式,就是在子線程中,用主線程中建立的hander調用sendMessage發送消息,把Message加入到主線程的MessageQueue中,等主線程的Looper從MessageQueue中取到這個消息時,會調用這個Message的target的handleMessage方法,這個target其實就是咱們發消息用到的handler,也就是調用了咱們重寫的handleMessage方法。

發消息:Handler.sendMessage(Message) 處理消息:Message.target.handleMessage(其中target就是發消息的handler)

2.2 Handler的post方法

final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("handler.sendMessage方式");
            }
        };


        new Thread(new Runnable() {
            @Override
            public void run() {
                // 使用post
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("handler.post方式");
                    }
                });
            }
        }).start();
複製代碼

在子線程中使用handler的post方法,也是能夠更新UI的,post方法須要傳入一個Runnalbe對象。咱們來看看post方法源碼

public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
複製代碼

能夠看到,先調用了getPostMessage構建了一個Message對象,而後調用了sendMessageDelayed方法,前面知道,sendMessage也是調用的這個方法,因此咱們只要關注怎麼構建的Message對象,看getPostMessage方法。

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        // post方法傳入的Runnable對象做爲Message的callback
        m.callback = r;
        return m;
    }
複製代碼

其實很簡單,就是把post方法傳入的參數做爲Message的callback來建立一個Message。

咱們再來回顧一下從MessageQueue中取出消息來對消息對處理,方法是Handler對dispatchMessage方法

public void dispatchMessage(Message msg) {
        // callback其實就是post方法傳入對Runnable對象
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 不會執行到這裏
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        // 至關於調用了post方法傳入對Runnable對象對run方法
        message.callback.run();
    }
複製代碼

能夠知道,咱們使用post發送消息,就是使用傳入對Runnable對象封裝成一個Message,而後加入到主線程中的MessageQueue,等主線程的Looper取出該消息處理時,由於Message.callback不爲空,而調用其run方法,也就是咱們調用post方法傳入的Runnable對象的run方法,且不會調用Hander的handleMessage方法。

發送消息:Handler.post(Runnable) 處理消息:Message.callback.run(callback爲調用post方法傳入的Runnable)

2.3 Activity的runOnUiThread方法

new Thread(new Runnable() {
            @Override
            public void run() {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("Activity的runOnUiThread方法");
                    }
                });
            }
        }).start();
複製代碼

咱們若是能在子線程中獲取到Activity對象,是能夠調用其runOnUiThread方法,來更新UI。咱們來看看Activity的runOnUiThread源碼。

public final void runOnUiThread(Runnable action) {
        // 若是不是UI線程,則調用Handler的post
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            // 若是是ui線程,則直接回調Runnable的run方法
            action.run();
        }
    }
複製代碼

若是是UI線程,就直接調用了傳入的Runnable對象的run方法,咱們主要是看非UI線程的邏輯。

若是不是UI線程,則直接使用mHandler的post方法,看來Activity的runOnUiThread方法也是基於Handler的post方法來實現的,後面的邏輯就是把傳入的Runnable封裝成Message發送出去,上面講過了,就再也不復述了。

咱們再看看這個mHandler的定義,其實就是Activity的成員屬性

final Handler mHandler = new Handler();
複製代碼

Activity是在主線程建立的,因此這個Handler也是在主線程中建立的,且持有的Looper爲主線程的Looper。那麼使用這個Handler調用post方法發出的消息,是加入到主線程的MessageQueue中,這樣就完成了子線程跟主線程的通訊。

發送消息:Activity. runOnUiThread(Runnable) 處理消息:Message.callback.run(callback爲runOnUiThread方法傳入的Runnable)

2.4 View的post方法

new Thread(new Runnable() {
            @Override
            public void run() {
                // 調用View的post方法
                btn.post(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("View.post方式");
                    }
                });
            }
        }).start();
複製代碼

咱們直接看View的post方法

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }
複製代碼

能夠看到這裏也是調用的Handler的post方法,跟Activity. runOnUiThread相似。

發送消息:View.post(Runnable) 處理消息:Message.callback.run(callback爲post方法傳入的Runnable對象)

總結一下:

  1. Handler.sendMessage: 把消息加入到主線程的MessageQueue中,主線程中的Looper從MessageQueue中取出消息,調用Message.target.handleMessage方法
  2. Handler.post: 基於Handler.sendMessage,把消息加入到主線程的MessageQueue中,主線程中的Looper從MessageQueue中取出消息,調用Message.callback.run方法
  3. Activity.runOnUiThread: 基於Handler.post
  4. View.post: 基於Handler.post

因此,以上子線程更新主線程UI的全部方式,都是依賴於Handler機制。

  1. AsyncTask

當咱們想在子線程中作耗時任務時,會考慮使用AsyncTask,咱們來舉個栗子,在子線程中去建立自定義的MyAsyncTask並執行它,在doInBackground中去模擬耗時操做:

public class AsyncTaskActivity extends AppCompatActivity {
    private static final String TAG = "AsyncTaskActivity";
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);
        btn = (Button) findViewById(R.id.btn);
        // 開啓一個子線程,去執行異步任務
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run, thread name:"  + Thread.currentThread().getName());
                MyAsyncTask asyncTask = new MyAsyncTask();
                asyncTask.execute();
            }
        }).start();
    }

    // 自定義AsyncTask,並重寫相關方法,並打印執行所在線程
    class MyAsyncTask extends AsyncTask<Void, Integer, Void>{

        @Override
        protected Void doInBackground(Void... voids) {
            Log.e(TAG, "doInBackground, thread name:"  + Thread.currentThread().getName());
            // 模擬耗時任務
            for (int i = 0; i < 5; i ++) {
                SystemClock.sleep(1000);
                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            Log.e(TAG, "onPreExecute, thread name:"  + Thread.currentThread().getName());
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            Log.e(TAG, "onPostExecute, thread name:"  + Thread.currentThread().getName());
            btn.setText("執行完了!");
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            Log.e(TAG, "onProgressUpdate, thread name:"  + Thread.currentThread().getName());
        }
    }
}
複製代碼

AsyncTask_log
能夠看到,咱們建立的子線程名爲Thread-4,而AsyncTask的方法所在線程以下:

  1. onPreExecute: Thread-4,其實就是調用AsyncTask的execute方法的線程
  2. doInBackground: AsyncTask #1,這實際上是AsyncTask的線程池中的一個線程
  3. onProgressUpdate: main,即主線程
  4. onPostExecute: main,即主線程

關於onPreExecute,這很好實現,不須要切換線程,直接回調就能夠;而doInBackground方法的執行,能夠直接取AsyncTask維持的線程池來執行就能夠。咱們重點關注onProgressUpdate和onPostExecute方法,是怎麼從子線程切換到主線程的。

咱們從AsyncTask的源碼中能夠看到這樣一個內部類

private static class InternalHandler extends Handler {
        public InternalHandler() {
            // 使用主線程的Looper去建立Handler
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // finish中主要調用onPostExecute
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    // 調用onProgressUpdate
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
複製代碼

從handleMessage方法看到,這裏切換到主線程,也是使用的Handler機制來實現的,可是爲何咱們無論在任何線程建立的AsyncTask去執行,最後均可以在主線程去回調這兩個方法呢?主要是建立InternalHandler時,使用的是主線程的Looper,這樣使用這個InternalHandler發送消息時,消息就會加入到主線程Looper對應的MessageQueue中,因此主線程Looper取出消息處理時,InternalHandler的handleMessage方法就是在主線程中回調的了。

因此AsyncTask其實就是基於線程池+Handler機制來實現的。

  1. 其餘使用Handler的地方

  2. RxJava: 子線程切換到主線程執行觀察者的回調方法(RxJava我不熟悉)

  3. Glide:圖片準備好之後的回顯

  4. LocalBroadcastManager: 傳統的廣播是基於binder實現的,本地廣播LocalBroadcastManager是基於Handler實現的

其實還有不少使用到handler機制的地方,就不一一舉例了,反正記住,Handler機制很重要。

4.3 處理Handler消息,是在哪一個線程?必定是建立Handler的線程麼?

之前總以爲,處理消息的線程,就是建立Handler的線程,可是上一篇文章的分析,咱們知道這樣說實際上是不許確的(由於咱們建立Handler一般使用的默認Looper)。

處理消息的線程,實際上是發送handler所持有的Looper所在的線程。

其實原理很好理解,咱們知道Handler的原理如圖

Handler流程圖
因此,消息的處理分發所在線程徹底取決於消息所在MessageQueue的線程,若是想要在某個線程中處理消息,只要作到把消息加入到那個線程所對應的MessageQueue中。

就像上面講到AsyncTask的例子,就算咱們在子線程建立了AsyncTask(即在子線程建立了用於通訊的Handler),但只要咱們建立Handler的時候,經過Looper.getMainLooper()傳入主線程的Looper ,那麼消息就加入到了主線程所對應的MessageQueue中,消息就是在主線程中處理的。

以下,咱們在子線程中建立一個handler,而後在主線程發送消息,由於建立handler使用的是子線程中的Looper,因此消息是在主線程中處理的。代碼以下:

public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh);
        btn = (Button) findViewById(R.id.btn);
        // 開啓一個子線程,去建立Handler
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
                    }
                };
                Looper.loop();
            }
        }).start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發送一個消息
                mHandler.sendEmptyMessage(100);
            }
        });
    }

}
複製代碼

log輸出,能夠看出,處理消息是在子線程中:

log
按照以前的說法,若是咱們想在主線程中處理消息,只要把消息加入到主線程的MessageQueue中,因此咱們能夠建立Looper時,傳入主線程的Looper,代碼以下:

public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh);
        btn = (Button) findViewById(R.id.btn);
        // 開啓一個子線程,去建立Handler
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                // 建立Handler傳入主線程的Looper
                mHandler = new Handler(Looper.getMainLooper()){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
                    }
                };
                Looper.loop();
            }
        }).start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發送一個消息
                mHandler.sendEmptyMessage(100);
            }
        });
    }
}
複製代碼

能夠看到,建立Handler使用了主線程的Looper後,的確消息是在主線程中處理的了:

log

4.4 是如何插入到MessageQueue中?

咱們以前說,全部handler.post和handler.sendMessage都會調用到Handler的sendMessageDelayed方法,方法以下:

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
複製代碼

這裏邏輯就很簡單了,直接調用了sendMessageAtTime方法,第一個參數爲Message,第二個參數爲SystemClock.uptimeMillis() + delayMillis,其中delayMillis爲延時的時間,單位爲毫秒,SystemClock.uptimeMillis() 爲開機到如今的時間(不包括休眠時間),單位爲毫秒。第二個參數主要是決定該Message在MessageQueue的順序,好比如今開機時間爲100s,發送一個延時20s的消息,則二者之和爲120s; 過了5秒,又發了一個延時5s的消息,則二者只喝爲105+5 = 110s。

sendMessageAtTime最後會調用到MessageQueue的enqueueMessage方法,咱們來看看這個方法:

boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            //  when = 開機到目前的時間 + 延時時間
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // 若是當前消息隊列爲空或者當前when小於隊頭when
            // 則把消息插入到隊頭
            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 {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 死循環,根據when尋找Message插入到MessageQueue合適的位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // MessageQueue中依次日後找到第一個Message.when大於當前Message.when的Message
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把當前Message插入到MessageQueue的合適位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                // 若是須要喚醒,則調用nativeWake去喚醒處理線程
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

從上面的代碼,能夠很容易看出,Message在MessageQueue中是根據when從小到大來排隊的,when是開機到如今的時間+延時時間。

好比,咱們假設開機時間爲100s,此時MessageQueue沒有消息,這時候發一個延時20s的消息,即when爲120000,則MessageQueue中的消息狀況如圖:

MessageQueue開始
過了5s,咱們又發了一個延時10s的消息,則when爲115000,此時MessageQueue如圖:

MessageQueue開始過了5s
又過了5s,咱們發了一個不延時的消息,即when爲110000,此時MessageQueue如圖:

MessageQueue又過了5s
因此,Message在MessageQueue中是根據when從小到大來排隊的,when是開機到如今的時間+延時時間。

4.5 MessageQueue的next會形成App的ANR麼

咱們知道Activity若是5s的事件都不能相應用戶的請求,則會ANR。咱們在來回顧下Looper.loop方法:

public static void loop() {
        final Looper me = myLooper();
        // 取到當前線程的MessageQueue
        final MessageQueue queue = me.mQueue;
        
        // 死循環
        for (;;) {
            // 調用MessageQueue.next,從隊列中取出消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
            // 對消息對分發
            msg.target.dispatchMessage(msg);

          // 省略無關代碼
        }
    }
複製代碼

在前面講過Looper.loop方法維持了App的運行,它是裏面使用了一個死循環,咱們App日常的操做(Activity的生命週期、點擊事件等)都是屬於調用了 msg.target.dispatchMessage(msg)對消息的處理。可是若是MessageQueue中沒有消息時,MessageQueue的next方法會阻塞,那它會致使App對ANR麼?

咱們來看看MessageQueue的next方法

Message next() {

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        // 死循環
        for (;;) {
            // 阻塞MessageQueue
            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) {
                        // 若是是延時消息,則算出須要阻塞的時間nextPollTimeoutMillis
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 若是不是延時顯示,則直接把消息返回,以供處理
                        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 {
                    // 沒有消息時,設置nextPollTimeoutMillis爲-1,阻塞MessageQueue
                    nextPollTimeoutMillis = -1;
                }
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 通常都會知足if條件,而後mBlocked設置爲true,繼續continue
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                //  省略無關代碼
            }

            pendingIdleHandlerCount = 0;

            nextPollTimeoutMillis = 0;
        }
    }

複製代碼

next方法中,首先設置一個死循環,而後調用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一個native方法,用於阻塞MessageQueue,主要是關注它的第二個參數nextPollTimeoutMillis,有以下三種可能:

  1. 若是nextPollTimeoutMillis=-1,一直阻塞不會超時。
  2. 若是nextPollTimeoutMillis=0,不會阻塞,當即返回。
  3. 若是nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時),若是其間有程序喚醒會當即返回。

咱們先繼續往下看,開始nextPollTimeoutMillis爲0,也就是不會阻塞,則繼續往下,這時候有三種狀況

  1. 延時消息,則直接算出目前須要延時的時間nextPollTimeoutMillis,注意,這時候並無把消息返回,而是繼續往下,設置mBlocked爲true,表示消息隊列已阻塞,並continue執行for循環體,再次執行nativePollOnce方法,這時候nextPollTimeoutMillis>0,則會致使MessageQueue休眠nextPollTimeoutMillis毫秒,接着應該會走到狀況2.
  2. 不是延時消息,則設置mBlocked爲false,表示消息隊列沒有阻塞,直接把消息返回,且把消息出隊。
  3. 若是消息爲空,則調用位置nextPollTimeoutMillis爲-1,繼續往下,設置mBlocked爲true,表示消息隊列已阻塞,並continue繼續for循環,這時候調用nativePollOnce會一直阻塞,且不會超時。

因此,當消息隊列爲空時,實際上是調用本地方法nativePollOnce,且第二個參數爲-1,它會致使當前線程阻塞,且不會超時,因此不會出現ANR的狀況,其實這是由Linux的管道機制(pipe)來保證的,當線程阻塞時,對CPU等資源等消耗時極低的,具體的原理能夠自行查閱。

那線程阻塞之後,何時才能再喚醒呢?記得以前咱們說消息加入MessageQueue的邏輯麼?咱們再來回顧一下enqueueMessage的流程:

boolean enqueueMessage(Message msg, long when) {

        synchronized (this) {

            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;
                // 若是MessageQueue爲空
                needWake = mBlocked;
            } else {
                // 若是MessageQueue中有消息
                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;
            }

            // 若是須要喚醒當前線程,則調用nativeWake方法
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

其實就是使用Handler發送消息,把消息加入到MessageQueue時,會判斷當前MessageQueue是否阻塞,若是阻塞了,則須要調用nativeWake方法去喚醒線程,而這個阻塞是在前面提到的MessageQueue的next方法中,MessageQueue沒有消息或者消息爲延時消息時設置的。 因此MessageQueue的next方法可能會阻塞線程,但不會形成ANR。

  1. 當MessageQueue沒有消息時,next方法中調用nativePollOnce致使線程阻塞,直到有新消息加入MesssageQueue時調用nativeWake來喚醒線程
  2. 當MessageQueue有消息時且隊頭消息爲延時消息時,next方法調用nativePollOnce致使線程阻塞nextPollTimeoutMillis的時間,中途有新消息加入MessageQueue時調用nativeWake能夠喚醒線程,也能夠等nextPollTimeoutMillis後自動喚醒線程
4.6 子線程使用Toast

有時候,咱們須要在子線程中直接彈出Toast來提示一些信息,代碼以下:

public class ToastActivity extends AppCompatActivity {
    private static final String TAG = "ToastActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toast);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子線程中彈出toast
                Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
            }
        }).start();
    }
}
複製代碼

運行程序,會發現程序會崩潰。是由於子線程不能更新UI麼?其實不是不這樣的。

  1. Toast是屬於系統Window,不受子線程更新UI限制。
  2. onCreate方法中,子線程可能能夠更新UI,由於子線程不能更新UI的檢測是在ViewRootImpl的checkThread完成的,而onCreate方法中,ViewRootImpl尚未建立,因此不會去檢測。

既然不是這兩方面的緣由,咱們來看看報錯的log吧

log
這跟咱們在子線程中直接使用handler好像報的錯誤相似,那咱們也跟使用hendler的套路同樣,先調用Looper.prepare而後再調用Looper.loop呢?代碼以下:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                // 子線程中彈出toast
                Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();

複製代碼

發現就能夠了,能夠看出Toast也是須要使用Handler,咱們來看看Toast的實現,直接看Toast中的一個內部類TN,它是一個IBinder實現類,咱們來看它的定義:

private static class TN extends ITransientNotification.Stub {
       final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                // 調用handleShow,處理顯示邏輯
                handleShow();
            }
        }
        final Handler mHandler = new Handler();    

        // 省略無關代碼

        WindowManager mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            // 這裏是Binder線程池,用handler切換到有Looper的線程
            mHandler.post(mShow);
        }
        
        // Toast的真實顯示
        public void handleShow() {
            if (mView != mNextView) {
                mView = mNextView;

                // 省略無關代碼

                // 把View加入到Window上
                mWM.addView(mView, mParams);
            }
        }
        
        //省略無關代碼
}
複製代碼

能夠看出,Toast的顯示,使用了Binder通訊,其實就是WindowManagerService會拿到TN對象,調用其show方法,可是這是Binder線程池中執行的,因此使用handler切換到調用Toast的show方法所在的線程去執行,這裏使用的就是handler.post,因此就須要調用Toast.show方法所在線程有Looper。最後調用的就是handleShow方法,把View加載到Window上。

總結一下Handler:

  1. Toast是系統Window來實現的
  2. Toast的顯示使用了IPC
  3. Toast的顯示使用了Handler機制
  4. 子線程可使用Toast,不過須要使用Handler的套路
4.7 Looper.loop能夠中止麼?

前面咱們知道,loop方法中是一個死循環,又由於代碼是順序執行的,因此它以後的代碼是得不到執行的,以下:

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);
        btn = (Button) findViewById(R.id.btn);
        // 開啓一個子線程,去執行異步任務
        new Thread(new Runnable() {
            @Override
            public void run() {
               
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
                 Log.e(TAG, "Looper.loop以前" );
                // Looper.loop方法是一個死循環
                Looper.loop();
                // 得不到執行
                Log.e(TAG, "Looper.loop以後" );
            }
        }).start();
    }
}
複製代碼

log如圖,只會打印loop方法以前的,loop以後的代碼得不到執行:

log
這樣咱們就要考慮一個問題了,並非全部線程都須要像主線程同樣一直運行下去,有些線程但願作完耗時任務後能回收,可是由於Looper.loop方法,致使線程只是阻塞,隨時有被喚醒的可能,不能釋放。那有什麼辦法能中止loop方法麼?

其實Looper提供了quit和quitSafely方法來中止Looper,咱們先來看看quit的用法,在點擊事件中調用了Looper的quit方法,修改後的代碼以下

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);
        btn = (Button) findViewById(R.id.btn);
        // 開啓一個子線程,去執行異步任務
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
                Log.e(TAG, "Looper.loop以前" );
                // Looper.loop方法是一個死循環
                Looper.loop();
                // 得不到執行
                Log.e(TAG, "Looper.loop以後" );
            }
        }).start();
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 調用Looper的quit方法,中止Looper
                mHandler.getLooper().quit();
            }
        });
    }
}
複製代碼

開始和以前同樣,Looper.loop後的方法不會獲得執行,咱們點擊按鈕後,Looper會中止,Looper.loop以後的代碼也能夠獲得執行,log以下:

log
咱們來看看Looper的quit和quitSafely的源碼:

public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
複製代碼

咱們發現,兩個方法都是調用了MessageQueue的quit方法,只是傳入的參數不一樣,咱們來看看MessageQueue的quit方法:

void quit(boolean safe) {
        synchronized (this) {
            if (mQuitting) {
                return;
            }

            // MessageQueue正在中止,用於next方法退出死循環
            mQuitting = true;

            if (safe) {
                // 刪除MessageQueue中的延時消息
                removeAllFutureMessagesLocked();
            } else {
                // 刪除MessageQueue中的全部消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
複製代碼

首先把mQuitting設置爲true,主要用於MessageQueue的next方法退出死循環,而後經過safe去判斷邏輯邏輯,這裏就能夠看出Looper的quit和quitSafely的區別了

  1. quit: 刪除MesageQueue中全部消息
  2. quitSafely: 刪除MessageQueue中的延時消息

咱們繼續看mQuitting對MessageQueue的next方法的影響,回到next方法,咱們只看關鍵性代碼:

Message next() {
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
              
                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;
                }

                // 判斷mQuitting
                if (mQuitting) {
                    dispose();
                    return null;
                }

            }

        }
    }
複製代碼

直接看最後的部分,對mQuitting作判斷,咱們以前在MessageQueue的quit方法中,會把這個屬性設置爲true,其實就是會影響到這裏。知足條件之後,調用了dispose方法,並返回了null。

咱們先來看dispose方法

private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }
複製代碼

其實就是調用了nativeDestroy方法,它是一個native方法,用於在底層中止MessageQueue。

這裏只是中止了MessageQueue的next中的死循環,Looper.loop方法中的死循環仍是沒有退出,咱們繼續看Looper.loop方法。

public static void loop() {
        final Looper me = myLooper();
       
        final MessageQueue queue = me.mQueue;

        for (;;) {
            // 當mQuitting爲true,queue.next方法返回了null
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 直接return,退出loop的死循環
                return;
            }
            // 省略無關代碼
        }
    }

複製代碼

前面咱們知道,當調用了Looper的quit或者quitSafely時,會設置當前線程的MessageQueue的 mQuitting爲true,而後致使了MessageQueue的next返回了null,而後直接return了,退出了loop中的死循環,這樣就完成了中止Looper的邏輯。

4.8 Handler的內存泄漏

咱們一般會使用以下的方式去使用handler來通訊

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        // 匿名內部類
        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // 處理消息
            }
        };

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發送延時100s的消息
                mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 使用leakcanary作內存泄漏檢測
        RefWatcher refWatcher = MyApplication.getRefWatcher(this);
        if (refWatcher != null) {
            refWatcher.watch(this);
        }
    }
}
複製代碼

可是會有一個問題,咱們進入這個頁面而後點擊按鈕,發送一個延時100s的消息,再退出這個Activity,這時候可能致使內存泄漏。

根本緣由是由於咱們建立的匿名內部類Handler對象持有了外部類Activity的對象,咱們知道,當使用handler發送消息時,會把handler做爲Message的target保存到MessageQueue,因爲延時了100s,因此這個Message暫時沒有獲得處理,這時候它們的引用關係爲MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,以下圖所示

Handler內存泄漏
當退出這個Activity時,由於Handler還持有Activity,因此gc時不能回收該Activity,致使了內存泄漏,使用LeakCanary檢測,效果以下圖所示:

LeakCanary-泄漏圖
固然,過了100s,延時消息獲得了處理,Activity對象屬於不可達的狀態時,會被回收。

那怎麼來解決Handler泄漏呢?主要有以下兩種方式:

  1. 靜態內部類+弱引用
  2. 移除MessageQueue中的消息
4.8.1 靜態內部類+弱引用

咱們知道,靜態內部類是不會引用外部類的對象的,可是既然靜態內部類對象沒有持有外部類的對象,那麼咱們怎麼去調用外部類Activity的方法呢?答案是使用弱引用。代碼以下:

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        
        // 建立Handler對象,把Activity對象傳入
        mHandler = new MyHandler(HandlerActivity.this);

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發送延時100s的消息
                mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
            }
        });
    }

    // 靜態內部類
    static class MyHandler extends Handler {
        private WeakReference<Activity> activityWeakReference;
        public  MyHandler(Activity activity) {
            activityWeakReference = new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 處理消息
            if (activityWeakReference != null) {
                Activity activity = activityWeakReference.get();
                // 拿到activity對象之後,調用activity的方法
                if (activity != null) {

                }
            }
        }
    }
}
複製代碼

首先,咱們自定義了一個靜態內部類MyHandler,而後建立MyHandler對象時傳入當前Activity的對象,供Hander以弱應用的方式持有,這個時候Activity就被強引用和弱引用兩種方式引用了,咱們繼續發起一個延時100s的消息,而後退出當前Activity,這個時候Activity的強引用就不存在了,只存在弱引用,gc運行時會回收掉只有弱引用的Activity,這樣就不會形成內存泄漏了。

但這個延時消息仍是存在於MessageQueue中,獲得這個Message被取出時,仍是會進行分發處理,只是這時候Activity被回收掉了,activity爲null,不能再繼續調用Activity的方法了。因此,其實這是Activity能夠被回收了,而Handler、Message都不能被回收。

至於爲何使用弱引用而沒有使用軟引用,其實很簡單,對比下二者回收前提條件就清楚了

  1. 弱引用(WeakReference): gc運行時,不管內存是否充足,只有弱引用的對象就會被回收
  2. 軟引用(SoftReference): gc運行時,只有內存不足時,只有軟引用的對象就會被回收

很明顯,當咱們Activity退出時,咱們但願無論內存是否足夠,都應該回收Activity對象,因此使用弱引用合適。

4.8.2 移除MessageQueue中的消息

咱們知道,內存泄漏的源頭是MessageQueue持有的Message持有了Handler持有了Activity,那咱們在合適的地方把Message從MessageQueue中移除,不就能夠解決內存泄漏了麼?

Handler爲咱們提供了removeCallbacksAndMessages等方法用於移除消息,好比,在Activity的onDestroy中調用Handler的removeCallbacksAndMessages,代碼以下:

@Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除MessageQueue中target爲該mHandler的Message
        mHandler.removeCallbacksAndMessages(null);
    }
複製代碼

其實就是在Activity的onDestroy方法中調用mHandler.removeCallbacksAndMessages(null),這樣就移除了MessageQueue中target爲該mHandler的Message,由於MessageQueue沒有引用該Handler發送的Message了,因此當Activity退出時,Message、Handler、Activity都是可回收的了,這樣就能解決內存泄漏的問題了。

5. 結尾

Handler的知識很少,但細節特別多,一旦久一點沒看就會忘記。 因此,無論是別人寫的仍是本身寫的,先把相關知識記下來,下次忘記了回來再看一下就好了。

6. 參考文章

  1. www.jianshu.com/p/592fb6bb6…
  2. www.jianshu.com/p/9631eebad…
  3. www.jianshu.com/p/67eb02c8b…
相關文章
相關標籤/搜索