1. 程式人生 > >圖解 Android 事件分發機制 和 handler 機制

圖解 Android 事件分發機制 和 handler 機制

在Android開發中,事件分發機制是一塊Android比較重要的知識體系,瞭解並熟悉整套的分發機制有助於更好的分析各種點選滑動失效問題,更好去擴充套件控制元件的事件功能和開發自定義控制元件,同時事件分發機制也是Android面試必問考點之一,如果你能把下面的一些事件分發圖當場畫出來肯定加分不少。廢話不多說,總結一句:事件分發機制很重要

Android 事件分發流###

關於Android 事件分發機制網上的博文很多,但是很多都是寫個Demo然後貼一下輸出的Log或者拿原始碼分析,然後一堆的註釋和說明,如果用心的去看肯定是收穫不少但是確實很難把整個流程說清和記住。曾經也是拼命想記住整個流程,但是一段時間又忘了,最後覺得分析這種問題和事件流的走向,一張圖來解釋和說明會清晰很多,下面我根據畫的一張事件分發流程圖,說明的事件從使用者點選之後,在不同函式不同返回值的情況的最終走向。

圖 1.

注:

  • 仔細看的話,圖分為3層,從上往下依次是Activity、ViewGroup、View
  • 事件從左上角那個白色箭頭開始,由Activity的dispatchTouchEvent做分發
  • 箭頭的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是呼叫父類實現。
  • dispatchTouchEvent和 onTouchEvent的框裡有個【true---->消費】的字,表示的意思是如果方法返回true,那麼代表事件就此消費,不會繼續往別的地方傳了,事件終止。
  • 目前所有的圖的事件是針對ACTION_DOWN的,對於ACTION_MOVE和ACTION_UP我們最後做分析。
  • 之前圖中的Activity 的dispatchTouchEvent 有誤(圖已修復),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消費了(終止傳遞)。

仔細看整個圖,我們得出事件流 走向的幾個結論(希望讀者專心的看下圖 1,多看幾遍,腦子有比較清晰的概念。)
1、如果事件不被中斷,整個事件流向是一個類U型圖,我們來看下這張圖,可能更能理解U型圖的意思。

圖 2.

所以如果我們沒有對控制元件裡面的方法進行重寫或更改返回值,而直接用super呼叫父類的預設實現,那麼整個事件流向應該是從Activity---->ViewGroup--->View 從上往下呼叫dispatchTouchEvent方法,一直到葉子節點(View)的時候,再由View--->ViewGroup--->Activity從下往上呼叫onTouchEvent方法。

2、dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止傳遞了(到達終點)(沒有誰能再收到這個事件)。看下圖中只要return true事件就沒再繼續傳下去了,對於return true我們經常說事件被消費了,消費了的意思就是事件走到這裡就是終點,不會往下傳,沒有誰能再收到這個事件了

 

圖 3.


3、dispatchTouchEvent 和 onTouchEvent return false的時候事件都回傳給父控制元件的onTouchEvent處理。

 

圖 4.

 

看上圖深藍色的線,對於返回false的情況,事件都是傳給父控制元件onTouchEvent處理。

  • 對於dispatchTouchEvent 返回 false 的含義應該是:事件停止往子View傳遞和分發同時開始往父控制元件回溯(父控制元件的onTouchEvent開始從下往上回傳直到某個onTouchEvent return true),事件分發機制就像遞迴,return false 的意義就是遞迴停止然後開始回溯。
  • 對於onTouchEvent return false 就比較簡單了,它就是不消費事件,並讓事件繼續往父控制元件的方向從下往上流動。

4、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
ViewGroup 和View的這些方法的預設實現就是會讓整個事件安裝U型完整走完,所以 return super.xxxxxx() 就會讓事件依照U型的方向的完整走完整個事件流動路徑),中間不做任何改動,不回溯、不終止,每個環節都走到。

Paste_Image.png

 

所以如果看到方法return super.xxxxx() 那麼事件的下一個流向就是走U型下一個目標,稍微記住上面這張圖,你就能很快判斷出下一個走向是哪個控制元件的哪個函式。
5、onInterceptTouchEvent 的作用

 

圖 5.

Intercept 的意思就攔截,每個ViewGroup每次在做分發的時候,問一問攔截器要不要攔截(也就是問問自己這個事件要不要自己來處理)如果要自己處理那就在onInterceptTouchEvent方法中 return true就會交給自己的onTouchEvent的處理,如果不攔截就是繼續往子控制元件往下傳。預設是不會去攔截的,因為子View也需要這個事件,所以onInterceptTouchEvent攔截器return super.onInterceptTouchEvent()和return false是一樣的,是不會攔截的,事件會繼續往子View的dispatchTouchEvent傳遞

6、ViewGroup 和View 的dispatchTouchEvent方法返回super.dispatchTouchEvent()的時候事件流走向。

 

圖 6

首先看下ViewGroup 的dispatchTouchEvent,之前說的return true是終結傳遞。return false 是回溯到父View的onTouchEvent,然後ViewGroup怎樣通過dispatchTouchEvent方法能把事件分發到自己的onTouchEvent處理呢,return true和false 都不行,那麼只能通過Interceptor把事件攔截下來給自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super預設實現就是去呼叫onInterceptTouchEvent,記住這一點
那麼對於View的dispatchTouchEvent return super.dispatchTouchEvent()的時候呢事件會傳到哪裡呢,很遺憾View沒有攔截器。但是同樣的道理return true是終結。return false 是回溯會父類的onTouchEvent,怎樣把事件分發給自己的onTouchEvent 處理呢,那隻能return super.dispatchTouchEvent,View類的dispatchTouchEvent()方法預設實現就是能幫你呼叫View自己的onTouchEvent方法的。

說了這麼多,不知道有說清楚沒有,我這邊最後總結一下:

  • 對於 dispatchTouchEvent,onTouchEvent,return true是終結事件傳遞。return false 是回溯到父View的onTouchEvent方法。
  • ViewGroup 想把自己分發給自己的onTouchEvent,需要攔截器onInterceptTouchEvent方法return true 把事件攔截下來。
  • ViewGroup 的攔截器onInterceptTouchEvent 預設是不攔截的,所以return super.onInterceptTouchEvent()=return false;
  • View 沒有攔截器,為了讓View可以把事件分發給自己的onTouchEvent,View的dispatchTouchEvent預設實現(super)就是把事件分發給自己的onTouchEvent。

ViewGroup和View 的dispatchTouchEvent 是做事件分發,那麼這個事件可能分發出去的四個目標

注:------> 後面代表事件目標需要怎麼做。
1、 自己消費,終結傳遞。------->return true ;
2、 給自己的onTouchEvent處理-------> 呼叫super.dispatchTouchEvent()系統預設會去呼叫 onInterceptTouchEvent,在onInterceptTouchEvent return true就會去把事件分給自己的onTouchEvent處理。
3、 傳給子View------>呼叫super.dispatchTouchEvent()預設實現會去呼叫 onInterceptTouchEvent 在onInterceptTouchEvent return false,就會把事件傳給子類。
4、 不傳給子View,事件終止往下傳遞,事件開始回溯,從父View的onTouchEvent開始事件從下到上回歸執行每個控制元件的onTouchEvent------->return false;
注: 由於View沒有子View所以不需要onInterceptTouchEvent 來控制元件是否把事件傳遞給子View還是攔截,所以View的事件分發呼叫super.dispatchTouchEvent()的時候預設把事件傳給自己的onTouchEvent處理(相當於攔截),對比ViewGroup的dispatchTouchEvent 事件分發,View的事件分發沒有上面提到的4個目標的第3點。

ViewGroup和View的onTouchEvent方法是做事件處理的,那麼這個事件只能有兩個處理方式:

1、自己消費掉,事件終結,不再傳給誰----->return true;
2、繼續從下往上傳,不消費事件,讓父View也能收到到這個事件----->return false;View的預設實現是不消費的。所以super==false。

ViewGroup的onInterceptTouchEvent方法對於事件有兩種情況:

1、攔截下來,給自己的onTouchEvent處理--->return true;
2、不攔截,把事件往下傳給子View---->return false,ViewGroup預設是不攔截的,所以super==false;

關於ACTION_MOVE 和 ACTION_UP####

上面講解的都是針對ACTION_DOWN的事件傳遞,ACTION_MOVE和ACTION_UP在傳遞的過程中並不是和ACTION_DOWN 一樣,你在執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個事件(如ACTION_DOWN)返回true,才會收到ACTION_MOVE和ACTION_UP的事件。具體這句話很多部落格都說了,但是具體含義是什麼呢?我們來看一下下面的具體分析。

上面提到過了,事件如果不被打斷的話是會不斷往下傳到葉子層(View),然後又不斷回傳到Activity,dispatchTouchEvent 和 onTouchEvent 可以通過return true 消費事件,終結事件傳遞,而onInterceptTouchEvent 並不能消費事件,它相當於是一個分叉口起到分流導流的作用,ACTION_MOVE和ACTION_UP 會在哪些函式被呼叫,之前說了並不是哪個函式收到了ACTION_DOWN,就會收到 ACTION_MOVE 等後續的事件的。
下面通過幾張圖看看不同場景下,ACTION_MOVE事件和ACTION_UP事件的具體走向並總結一下規律。

1、我們在ViewGroup1 的dispatchTouchEvent 方法返回true消費這次事件

ACTION_DOWN 事件從(Activity的dispatchTouchEvent)--------> (ViewGroup1 的dispatchTouchEvent) 後結束傳遞,事件被消費(如下圖紅色的箭頭程式碼ACTION_DOWN 事件的流向)。

//列印日誌
Activity | dispatchTouchEvent --> ACTION_DOWN 
ViewGroup1 | dispatchTouchEvent --> ACTION_DOWN
---->消費

在這種場景下ACTION_MOVE和ACTION_UP 將如何呢,看下面的打出來的日誌

Activity | dispatchTouchEvent --> ACTION_MOVE 
ViewGroup1 | dispatchTouchEvent --> ACTION_MOVE
----
TouchEventActivity | dispatchTouchEvent --> ACTION_UP 
ViewGroup1 | dispatchTouchEvent --> ACTION_UP
----

下圖中
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

2、我們在ViewGroup2 的dispatchTouchEvent 返回true消費這次事件

Activity | dispatchTouchEvent --> ACTION_DOWN 
ViewGroup1 | dispatchTouchEvent --> ACTION_DOWN
ViewGroup1 | onInterceptTouchEvent --> ACTION_DOWN
ViewGroup2 | dispatchTouchEvent --> ACTION_DOWN
---->消費
Activity | dispatchTouchEvent --> ACTION_MOVE 
ViewGroup1 | dispatchTouchEvent --> ACTION_MOVE
ViewGroup1 | onInterceptTouchEvent --> ACTION_MOVE
ViewGroup2 | dispatchTouchEvent --> ACTION_MOVE
----
TouchEventActivity | dispatchTouchEvent --> ACTION_UP 
ViewGroup1 | dispatchTouchEvent --> ACTION_UP
ViewGroup1 | onInterceptTouchEvent --> ACTION_UP
ViewGroup2 | dispatchTouchEvent --> ACTION_UP
----

紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

Paste_Image.png

3、我們在View 的dispatchTouchEvent 返回true消費這次事件
這個我不就畫圖了,效果和在ViewGroup2 的dispatchTouchEvent return true的差不多,同樣的收到ACTION_DOWN 的dispatchTouchEvent函式都能收到 ACTION_MOVE和ACTION_UP。
所以我們就基本可以得出結論如果在某個控制元件的dispatchTouchEvent 返回true消費終結事件,那麼收到ACTION_DOWN 的函式也能收到 ACTION_MOVE和ACTION_UP。

4、我們在View 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

5、我們在ViewGroup 2 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 


6、我們在ViewGroup 1 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

7、我們在Activity 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

8、我們在View的dispatchTouchEvent 返回false並且Activity 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

9、我們在View的dispatchTouchEvent 返回false並且ViewGroup 1 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

10、我們在View的dispatchTouchEvent 返回false並且在ViewGroup 2 的onTouchEvent 返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

11、我們在ViewGroup2的dispatchTouchEvent 返回false並且在ViewGroup1 的onTouchEvent返回true消費這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

12、我們在ViewGroup2的onInterceptTouchEvent 返回true攔截此次事件並且在ViewGroup 1 的onTouchEvent返回true消費這次事件。
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向

 

一下子畫了好多圖,還有好幾種情況就不再畫了,相信你也看出規律了,對於在onTouchEvent消費事件的情況:在哪個View的onTouchEvent 返回true,那麼ACTION_MOVE和ACTION_UP的事件從上往下傳到這個View後就不再往下傳遞了,而直接傳給自己的onTouchEvent 並結束本次事件傳遞過程。

對於ACTION_MOVE、ACTION_UP總結:ACTION_DOWN事件在哪個控制元件消費了(return true), 那麼ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent)做事件分發往下傳,就只會傳到這個控制元件,不會繼續往下傳,如果ACTION_DOWN事件是在dispatchTouchEvent消費,那麼事件到此為止停止傳遞,如果ACTION_DOWN事件是在onTouchEvent消費的,那麼會把ACTION_MOVE或ACTION_UP事件傳給該控制元件的onTouchEvent處理並結束傳遞。

如下示例展示了UI Thread與Child Thread之間互相傳送訊息,同時在UI Thread與Child Thread中分別定義Handler。這樣如果沒有mCount的判斷,這段程式會一直死迴圈列印下去。

public class MainActivity extends ActionBarActivity {
    private int mCount = 0;
    private Handler mHandlerThr = null;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d(null, ">>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what="+msg.what);
            //接收發送到UI執行緒的訊息,然後向執行緒中的Handler傳送msg 1。
            mHandlerThr.sendEmptyMessage(1);
            mCount++;
            if (mCount >= 3) {
                //由於mHandlerThr是在Child Thread建立,Looper手動死迴圈阻塞,所以需要quit。
                mHandlerThr.getLooper().quit();
            }
        }
    };

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

        initData();
    }

    @Override
    protected void onStop() {
        super.onStop();
        //刪除所有call與msg
        mHandler.removeCallbacksAndMessages(null);
    }

    private void initData() {
        Log.d(null, ">>>>>>>>>>>>>UI# begin start thread!!!");
        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                mHandlerThr = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.d(null, ">>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=" + msg.what);
                        //接收發送到子執行緒的訊息,然後向UI執行緒中的Handler傳送msg 0。
                        mHandler.sendEmptyMessage(0);
                    }
                };

                Log.d(null, ">>>>>>>>>>>>>Child# begin start send msg!!!");
                //Activity中啟動Thread,在Thread結束前傳送msg 0到UI Thread。
                mHandler.sendEmptyMessage(0);

                Looper.loop(); //不能在這個後面新增程式碼,程式是無法執行到這行之後的。
            }
        }.start();
    }
}

執行結果展示如下:

>>>>>>>>>>>>>UI# begin start thread!!!
>>>>>>>>>>>>>Child# begin start send msg!!!
>>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0
>>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=1
>>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0
>>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=1
>>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0

怎麼樣,這和你平時用的Handler一樣吧,對於Handler非同步處理的簡單基礎示例先說到這,接下來依據上面示例的寫法分析原因與原始碼原理。

3 分析Android 5.1.1(API 22)非同步訊息機制原始碼

3-1 看看Handler的例項化過程原始碼

3-1-1 Handler例項化原始碼

從哪著手分析呢?當然是例項化建構函式呀,所以我們先從Handler的預設建構函式開始分析,如下:

    /**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
    public Handler() {
        this(null, false);
    }

通過註釋也能看到,預設建構函式沒有引數,而且調運了帶有兩個引數的其他建構函式,第一個引數傳遞為null,第二個傳遞為false。

這個建構函式得到的Handler預設屬於當前執行緒,而且如果當前執行緒如果沒有Looper則通過這個預設構造例項化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;
    }

可以看到,在第11行呼叫了mLooper = Looper.myLooper();語句,然後獲取了一個Looper物件mLooper ,如果mLooper例項為空,則會丟擲一個執行時異常(Can’t create handler inside thread that has not called Looper.prepare()!)。

3-1-2 Looper例項化原始碼

好奇的你指定在想什麼時候mLooper 物件才可能為空呢?很簡單,跳進去看下吧,Looper類的靜態方法myLooper如下:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

咦?這裡好簡單。單單就是從sThreadLocal物件中get了一個Looper物件返回。跟蹤了一下sThreadLocal物件,發現他定義在Looper中,是一個static final型別的ThreadLocal<Looper>物件(在Java中,一般情況下,通過ThreadLocal.set() 到執行緒中的物件是該執行緒自己使用的物件,其他執行緒是不需要訪問的,也訪問不到的,各個執行緒中訪問的是不同的物件。)。所以可以看出,如果sThreadLocal中有Looper存在就返回Looper,沒有Looper存在自然就返回null了。

這時候你一定有疑惑,既然這裡是通過sThreadLocal的get獲得Looper,那指定有地方對sThreadLocal進行set操作吧?是的,我們在Looper類中跟蹤發現如下:

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

看著這個Looper的static方法prepare沒有?這段程式碼首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則建立一個新的Looper設定進去。

那就看下Looper的例項化,如下:

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

可以看見Looper建構函式無非就是建立了一個MessageQueue(它是一個訊息佇列,用於將所有收到的訊息以佇列的形式進行排列,並提供入隊和出隊的方法。)和貨到當前Thread例項引用而已。通過這裡可以發現,一個Looper只能對應了一個MessageQueue。

你可能會說上面的例子在子執行緒中明明先調運的是Looper.prepare();方法,這裡怎麼有引數了?那就繼續看吧,如下:

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

可以看見,prepare()僅僅是對prepare(boolean quitAllowed) 的封裝而已,預設傳入了true,也就是將MessageQueue物件中的quitAllowed標記標記為true而已,至於MessageQueue後面會分析。

稀奇古怪的事情來了!如果你足夠留意上面的例子,你會發現我們在UI Thread中建立Handler時沒有呼叫Looper.prepare();,而在initData方法中建立的Child Thread中首先就調運了Looper.prepare();。你指定很奇怪吧?UI Thread為啥不需要呢?上面原始碼分析明明需要先保證mLooper物件不為null呀?

這是由於在UI執行緒(Activity等)啟動的時候系統已經幫我們自動呼叫了Looper.prepare()方法。

那麼在哪啟動的呢?這個涉及Android系統架構問題比較多,後面文章會分析Activity的啟動流程。這裡你只要知道,以前一直都說Activity的人口是onCreate方法,其實android上一個應用的入口應該是ActivityThread類的main方法就行了。

所以為了解開UI Thread為何不需要建立Looper物件的原因,我們看下ActivityThread的main方法,如下:

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

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

        Looper.loop();

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

看見22行沒?沒說錯吧?Looper.prepareMainLooper();,我們跳到Looper看下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();
        }
    }

可以看到,UI執行緒中會始終存在一個Looper物件(sMainLooper 儲存在Looper類中,UI執行緒通過getMainLooper方法獲取UI執行緒的Looper物件),從而不需要再手動去呼叫Looper.prepare()方法了。如下Looper類提供的get方法:

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

看見沒有,到這裡整個Handler例項化與為何子執行緒在例項化Handler之前需要先調運Looper.prepare();語句的原理分析完畢。

3-1-3 Handler與Looper例項化總結

到此先初步總結下上面關於Handler例項化的一些關鍵資訊,具體如下:

  1. 在主執行緒中可以直接建立Handler物件,而在子執行緒中需要先呼叫Looper.prepare()才能建立Handler物件,否則執行丟擲”Can’t create handler inside thread that has not called Looper.prepare()”異常資訊。

  2. 每個執行緒中最多隻能有一個Looper物件,否則丟擲異常。

  3. 可以通過Looper.myLooper()獲取當前執行緒的Looper例項,通過Looper.getMainLooper()獲取主(UI)執行緒的Looper例項。

  4. 一個Looper只能對應了一個MessageQueue。

  5. 一個執行緒中只有一個Looper例項,一個MessageQueue例項,可以有多個Handler例項。

Handler物件也建立好了,接下來就該用了吧,所以下面咱們從Handler的收發訊息角度來分析分析原始碼。

3-2 繼續看看Handler訊息收發機制原始碼

3-2-1 通過Handler發訊息到訊息佇列

還記得上面的例子嗎?我們在Child Thread的最後通過主執行緒的Handler物件調運sendEmptyMessage方法傳送出去了一條訊息。

當然,其實Handler類提供了許傳送訊息的方法,我們這個例子只是用了最簡單的傳送一個empty訊息而已,有時候我們會先定義一個Message,然後通過Handler提供的其他方法進行傳送。通過分析Handler原始碼發現Handler中提供的很多個傳送訊息方法中除了sendMessageAtFrontOfQueue()方法之外,其它的傳送訊息方法最終都呼叫了sendMessageAtTime()方法。所以,咱們先來看下這個sendMessageAtTime方法,如下:

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

再看下Handler中和其他傳送方法不同的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);
    }

對比上面兩個方法可以發現,表面上說Handler的sendMessageAtFrontOfQueue方法和其他傳送方法不同,其實實質是相同的,僅僅是sendMessageAtFrontOfQueue方法是sendMessageAtTime方法的一個特例而已(sendMessageAtTime最後一個引數傳遞0就變為了sendMessageAtFrontOfQueue方法)。所以咱們現在繼續分析sendMessageAtTime方法,如下分析:

sendMessageAtTime(Message msg, long uptimeMillis)方法有兩個引數;msg是我們傳送的Message物件,uptimeMillis表示傳送訊息的時間,uptimeMillis的值等於從系統開機到當前時間的毫秒數再加上延遲時間。

在該方法的第二行可以看到queue = mQueue,而mQueue是在Handler例項化時建構函式中例項化的。在Handler的建構函式中可以看見mQueue = mLooper.mQueue;,而Looper的mQueue物件上面分析過了,是在Looper的建構函式中建立的一個MessageQueue。

接著第9行可以看到,上面說的兩個引數和剛剛得到的queue物件都傳遞到了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);
    }
  •  

這個方法首先將我們要傳送的訊息Message的target屬性設定為當前Handler物件(進行關聯);接著將msg與uptimeMillis這兩個引數都傳遞到MessageQueue(訊息佇列)的enqueueMessage()方法中,所以接下來我們就繼續分析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("MessageQueue", 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;
    }

通過這個方法名可以看出來通過Handler傳送訊息實質就是把訊息Message新增到MessageQueue訊息佇列中的過程而已。

通過上面遍歷等next操作可以看出來,MessageQueue訊息佇列對於訊息排隊是通過類似c語言的連結串列來儲存這些有序的訊息的。其中的mMessages物件表示當前待處理的訊息;然後18到49行可以看出,訊息插入佇列的實質就是將所有的訊息按時間(uptimeMillis引數,上面有介紹)進行排序。所以還記得上面sendMessageAtFrontOfQueue方法嗎?它的實質就是把訊息新增到MessageQueue訊息佇列的頭部(uptimeMillis為0,上面有分析)。

到此Handler的傳送訊息及傳送的訊息如何存入到MessageQueue訊息佇列的邏輯分析完成。

那麼問題來了!既然訊息都存入到了MessageQueue訊息佇列,當然要取出來訊息吧,不然存半天有啥意義呢?我們知道MessageQueue的物件在Looper建構函式中例項化的;一個Looper對應一個MessageQueue,所以說Handler傳送訊息是通過Handler建構函式裡拿到的Looper物件的成員MessageQueue的enqueueMessage方法將訊息插入佇列,也就是說出佇列一定也與Handler和Looper和MessageQueue有關係。

還記不記得上面例項部分中Child Thread最後調運的Looper.loop();方法呢?這個方法其實就是取出MessageQueue訊息佇列裡的訊息方法。具體在下面分析。

3-2-2 通過Handler接收發送的訊息

先來看下上面例子中Looper.loop();這行程式碼調運的方法,如下:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;

        // 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();

        for (;;) {
            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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

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

可以看到,第6行首先得到了當前執行緒的Looper物件me,接著第10行通過當前Looper物件得到與Looper物件一一對應的MessageQueue訊息佇列(也就類似上面傳送訊息部分,Handler通過myLoop方法得到Looper物件,然後獲取Looper的MessageQueue訊息佇列物件)。17行進入一個死迴圈,18行不斷地呼叫MessageQueue的next()方法,進入MessageQueue這個類檢視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();
            }

            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) {
                        // 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 (false) Log.v("MessageQueue", "Returning message: " + msg);
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                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(pendingIdleHandlerCount, 4)];
                }
                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("MessageQueue", "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;
        }
    }

可以看出來,這個next方法就是訊息佇列的出隊方法(與上面分析的MessageQueue訊息佇列的enqueueMessage方法對比)。可以看見上面程式碼就是如果當前MessageQueue中存在待處理的訊息mMessages就將這個訊息出隊,然後讓下一條訊息成為mMessages,否則就進入一個阻塞狀態(在上面Looper類的loop方法上面也有英文註釋,明確說到了阻塞特性),一直等到有新的訊息入隊。

繼續看loop()方法的第30行(msg.target.dispatchMessage(msg);),每當有一個訊息出隊就將它傳遞到msg.target的dispatchMessage()方法中。其中這個msg.target其實就是上面分析Handler傳送訊息程式碼部分Handler的enqueueMessage方法中的msg.target = this;語句,也就是當前Handler物件。所以接下來的重點自然就是回到Handler類看看我們熟悉的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);
        }
    }

可以看見dispatchMessage方法中的邏輯比較簡單,具體就是如果mCallback不為空,則呼叫mCallback的handleMessage()方法,否則直接呼叫Handler的handleMessage()方法,並將訊息物件作為引數傳遞過去。

這樣我相信大家就都明白了為什麼handleMessage()方法中可以獲取到之前傳送的訊息了吧!

對了,既然上面說了獲取訊息在MessageQueue訊息佇列中是一個死迴圈的阻塞等待,所以Looper的quit方法也很重要,這樣在不需要時可以退出這個死迴圈,如上面例項部分使用所示。

3-2-3 結束MessageQueue訊息佇列阻塞死迴圈原始碼分析

如下展示了Looper類的quit方法原始碼:

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

看見沒有,quit方法實質就是調運了MessageQueue訊息佇列的quit,如下:

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

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

看見上面2到4行程式碼沒有,通過判斷標記mQuitAllowed來決定該訊息佇列是否可以退出,然而當mQuitAllowed為fasle時丟擲的異常竟然是”Main thread not allowed to quit.”,Main Thread,所以可以說明Main Thread關聯的Looper一一對應的MessageQueue訊息佇列是不能通過該方法退出的。

你可能會疑惑這個mQuitAllowed在哪設定的?

其實他是MessageQueue建構函式傳遞引數傳入的,而MessageQueue物件的例項化是在Looper的建構函式實現的,所以不難發現mQuitAllowed引數實質是從Looper的構函式傳入的。上面例項化Handler模組原始碼分析時說過,Looper例項化是在Looper的靜態方法prepare(boolean quitAllowed)中處理的,也就是說mQuitAllowed是由Looper.prpeare(boolean quitAllowed)引數傳入的。追根到底說明mQuitAllowed就是Looper.prpeare的引數,我們預設調運的Looper.prpeare();其中對mQuitAllowed設定為了true,所以可以通過quit方法退出,而主執行緒ActivityThread的main中使用的是Looper.prepareMainLooper();,這個方法裡對mQuitAllowed設定為false,所以才會有上面說的”Main thread not allowed to quit.”。

回到quit方法繼續看,可以發現實質就是對mQuitting標記置位,這個mQuitting標記在MessageQueue的阻塞等待next方法中用做了判斷條件,所以可以通過quit方法退出整個當前執行緒的loop迴圈。

到此整個Android的一次完整非同步訊息機制分析使用流程結束。接下來進行一些總結提升與拓展。

3-3 簡單小結下Handler整個使用過程與原理

通過上面的原始碼分析原理我們可以總結出整個非同步訊息處理流程的關係圖如下:

這裡寫圖片描述

這幅圖很明顯的表達出了Handler非同步機制的來龍去脈,不做過多解釋。

上面例項部分我們只是演示了Handler的區域性方法,具體Handler還有很多方法,下面詳細介紹。

3-4 再來看看Handler原始碼的其他常用方法

在上面例子中我們只是演示了傳送訊息的sendEmptyMessage(int what)方法,其實Handler有如下一些傳送方式:

sendMessage(Message msg); sendEmptyMessage(int what);

sendEmptyMessageDelayed(int what, long delayMillis);sendEmptyMessageAtTime(int what, long uptimeMillis); sendMessageDelayed(Message msg, long delayMillis); sendMessageAtTime(Message msg, long uptimeMillis); sendMessageAtFrontOfQueue(Message msg);方法。

這些方法不再做過多解釋,用法雷同,頂一個Message決定啥時傳送到target去。

post(Runnable r); postDelayed(Runnable r, long delayMillis);等post系列方法。

該方法實質原始碼其實就是如下:

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
  •  

額,原來post方法的實質也是調運sendMessageDelayed()方法去處理的額,看見getPostMessage(r)方法沒?如下原始碼:

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

如上方法僅僅是將訊息的callback欄位指定為傳入的Runnable物件r。其實這個Message物件的m.callback就是上面分析Handler接收訊息回撥處理dispatchMessage()方法中調運的。在Handler的dispatchMessage方法中首先判斷如果Message的callback等於null才會去呼叫handleMessage()方法,否則就呼叫handleCallback()方法。那就再看下Handler的handleCallback()方法原始碼,如下:

    private static void handleCallback(Message message) {
        message.callback.run();
    }

額,這裡竟然直接執行了Runnable物件的run()方法。所以說我們在Runnable物件的run()方法裡更新UI的效果完全和在handleMessage()方法中更新UI相同,特別強調這個Runnable的run方法還在當前執行緒中阻塞執行,沒有建立新的執行緒(很多人以為是Runnable就建立了新執行緒)。

Activity.runOnUiThread(Runnable);方法。

首先看下Activity的runOnUiThread方法原始碼:

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

看見沒有,實質還是在UI執行緒中執行了Runnable的run方法。不做過多解釋。

 

View.post(Runnable);和View.postDelayed(Runnable action, long delayMillis);方法。

首先看下View的postDelayed方法原始碼:

    public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        return true;
    }

看見沒有,實質還是在UI執行緒中執行了Runnable的run方法。不做過多解釋。

到此基本上關於Handler的所有傳送訊息方式都被解釋明白了。既然會用了基本的那就得提高下,接下來看看關於Message的一點優化技巧。

3-5 關於Handler傳送訊息的一點優化分析

還記得我們說過,當傳送一個訊息時我們首先會new一個Message物件,然後再發送嗎?你是不是覺得每次new Message很浪費呢?那麼我們就來分析一下這個問題。

如下是我們正常傳送訊息的程式碼區域性片段:

    Message message = new Message();
    message.arg1 = 110;
    message.arg2 = 119;
    message.what = 0x120;
    message.obj = "Test Message Content!";

    mHandler.sendMessage(message);

相信很多初學者都是這麼傳送訊息的吧?當有大量多次傳送時如上寫法會不太高效。不賣關子,先直接看達到同樣效果的優化寫法,如下:

mHandler.sendMessage(mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\""));
  • 1

咦?怎麼send時沒例項化Message?這是咋回事?我們看下

mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\"")這一段程式碼,obtainMessage是Handler提供的一個方法,看下原始碼:

    public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
    {
        return Message.obtain(this, what, arg1, arg2, obj);
    }
  •  

這方法竟然直接調運了Message類的靜態方法obtain,我們再去看看obtain的原始碼,如下:

    public static Message obtain(Handler h, int what, 
            int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

看見沒有?首先又調運一個無參的obtain方法,然後設定Message各種引數後返回。我們繼續看下這個無參方法,如下:

    /**
     * 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();
    }

真相大白了!看見註釋沒有?從整個Messge池中返回一個新的Message例項,在許多情況下使用它,因為它能避免分配新的物件。

所以看見這兩種獲取Message寫法的優缺點沒有呢?明顯可以看見通過呼叫Handler的obtainMessage方法獲取Message物件就能避免建立物件,從而減少記憶體的開銷了。所以推薦這種寫法!!!

3-6 關於Handler導致記憶體洩露的分析與解決方法

正如上面我們例項部分的程式碼,使用Android Lint會提示我們這樣一個Warning,如下:

In Android, Handler classes should be static or leaks might occur.
  • 1

意思是說在Android中Handler類應該是靜態的否則可能發生洩漏。

啥是記憶體洩漏呢?

Java通過GC自動檢查記憶體中的物件,如果GC發現一個或一組物件為不可到達狀態,則將該物件從記憶體中回收。也就是說,一個物件不被任何引用所指向,則該物件會在被GC發現的時候被回收;另外,如果一組物件中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個物件A和B互相持有引用,但沒有任何外部物件持有指向A或B的引用),這仍然屬於不可到達,同樣會被GC回收。本該被回收的物件沒被回收就是記憶體洩漏。

Handler中怎麼洩漏的呢?

當使用內部類(包括匿名類)來建立Handler的時候,Handler物件會隱式地持有一個外部類物件(通常是一個Activity)的引用。而Handler通常會伴隨著一個耗時的後臺執行緒一起出現,這個後臺執行緒在任務執行完畢之後,通過訊息機制通知Handler,然後Handler把訊息傳送到UI執行緒。然而,如果使用者在耗時執行緒執行過程中關閉了Activity(正常情況下Activity不再被使用,它就有可能在GC檢查時被回收掉),由於這時執行緒尚未執行完,而該執行緒持有Handler的引用,這個Handler又持有Activity的引用,就導致該Activity暫時無法被回收(即記憶體洩露)。

Handler記憶體洩漏解決方案呢?

方案一:通過程式邏輯來進行保護

  1. 在關閉Activity的時候停掉你的後臺執行緒。執行緒停掉了,就相當於切斷了Handler和外部連線的線,Activity自然會在合適的時候被回收。

  2. 如果你的Handler是被delay的Message持有了引用,那麼使用相應的Handler的removeCallbacks()方法,把訊息物件從訊息佇列移除就行了(如上面的例子部分的onStop中程式碼)。

方案二:將Handler宣告為靜態類

靜態類不持有外部類的物件,所以你的Activity可以隨意被回收。程式碼如下:

static class TestHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

這時你會發現,由於Handler不再持有外部類物件的引用,導致程式不允許你在Handler中操作Activity中的物件了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference),如下:

static class TestHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    TestHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

如上就是關於Handler記憶體洩漏的分析及解決方案。

可能在你會用了Handler之後見過HandlerThread這個關鍵字,那我們接下來就看看HandlerThread吧。

4 關於Android非同步訊息處理機制進階的HandlerThread原始碼分析

4-1 Android 5.1.1(API 22) HandlerThread原始碼

很多人在會使用Handler以後會發現有些程式碼裡出現了HandlerThread,然後就分不清HandlerThread與Handler啥關係,咋回事之類的。這裡就來分析分析HandlerThread的原始碼。如下:

public class HandlerThread extends Thread {
    //執行緒的優先順序
    int mPriority;
    //執行緒的id
    int mTid = -1;
    //一個與Handler關聯的Looper物件
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        //設定優先順序為預設執行緒
        mPriority = android.os.Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    //可重寫方法,Looper.loop之前線上程中需要處理的其他邏輯在這裡實現
    protected void onLooperPrepared() {
    }
    //HandlerThread執行緒的run方法
    @Override
    public void run() {
        //獲取當前執行緒的id
        mTid = Process.myTid();
        //建立Looper物件
        //這就是為什麼我們要在呼叫執行緒的start()方法後才能得到Looper(Looper.myLooper不為Null)
        Looper.prepare();
        //同步程式碼塊,當獲得mLooper物件後,喚醒所有執行緒
        synchronized