天天看點

百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

前言

Handler 是Android中常用的異步通信的一個類,Android是一個消息驅動的作業系統,各種類型的消息都是由Handler發出,再由Handler處理,那麼對于Handler機制的了解就至關重要。

目錄

1. 作用

2. 基本用法

3. 源碼解析

3.1 為什麼 Handler 能夠切換線程執行?
3.2 Handler.post(Runnable) 方法是運作在新的線程嗎?
3.3 Handler(Callback) 跟 Handler() 這兩個構造方法的差別在哪?
3.4 子線程可以建立 Handler 嗎?
3.5 為什麼主線程不用調用 Looper.prepare() ?
3.6 為什麼建立 Message 對象推薦使用 Message.obtain()擷取?
3.7 梳理

4. 常見問題&技巧

4.1 為什麼 Handler 會造成記憶體洩漏?
4.2 怎麼防止 Handler 記憶體洩漏?
4.3 Loop.loop() 為什麼不會造成應用卡死?

5. 總結

1. 作用

Handler 是一種用于線程間的消息傳遞機制。

因為 Android 中不允許在非主線程更新UI,是以最常使用的地方就是

2.基本用法

step1:建立Handler執行個體用于子線程擷取某些資料後進行UI的更新。

//1.自定義Handler類
static class CustomHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        //更新UI等操作
    }
}

CustomHandler customHandler = new CustomHandler();

//2.内部類
Handler innerHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        //更新UI等操作
    }
};

//3.callback方式
Handler callbackHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        //更新UI等操作
        return true;
    }
});
           

step2:發送消息

//1.發送普通消息
 Message msg = Message.obtain();
 msg.what = 0; //辨別
 msg.obj = "這是消息體"; //消息内容
 innerHandler.sendMessage(msg);

 //2.發送Runnale消息
 innerHandler.post(new Runnable() {
     @Override
     public void run() {
         //更新UI等操作,消息接收後執行此方法
     }
 });
           

Handler 的建立以及消息的發送都有很多種方法,各種方式的異同會在下面講到。

3. 源碼分析

我們在發送 Message 的時候在子線程,為什麼執行的時候就切換成了主線程?想要知道答案,基本就要把 Handler 的運作流程給了解一遍。

因為最終的處理是在 handleMessage方法中進行的,是以我們看看 handleMessage方法是怎麼被調用起來的。

先打個 debug , 看看調用鍊:

百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

畫個圖直覺一點:

百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

可能有點奇怪,整個調用流程都沒有出現我們發送消息的方法,那我們發送的 Message 對象在哪裡被使用了呢?

看下上圖的 Step 2 ,在 loop() 方法裡面調用了 msg.target.dispatchMessage(msg) 方法,debug 中檢視 msg 對象的屬性,發現這個 msg 正是我們發送的那個 Message對象,這個 target 就是在 MainActivity 中建立的 Handler 對象。

也就是說,我們發送消息後,不知道什麼原因,Looper.loop() 方法内會拿到我們發送的消息,并且最終會調用發送該消息的 Handler 的 handleMessage(Message msg)方法。先看看 loop() 方法是怎麼拿到我們的 Message 的:

// Looper.java ,省略部分代碼
loop(){
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block  , 從隊列取出一個msg
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg); //Handler處理消息
        msg.recycleUnchecked();  //回收msg
    }
}
           

首先,loop()方法會判斷目前線程是否已經調用了 Looper.prepare(),如果沒有,則抛異常,這就是我們建立非主線程的 Handler 為什麼要調用Looper.prepare()的原因。而主線程中會在上面流程圖的 Step 1 中,即 ActivityThread.main() 方法裡面調用了 prepare 方法,是以我們建立預設(主線程)的 Handler 不需要額外建立 Looper 。

loop() 裡面是一個死循環,隻有當msg為空時才退出該方法。msg 是從 queue.next 中取出來的,這個 queue 就是我們經常聽到的消息隊列了(MessageQueue ),看看 next 方法的實作:

//MessageQueue.java ,删減部分代碼
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        //如果隊列已經停止了(quit or dispose)
        return null;
    }
    for (;;) {
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();   //擷取目前時間
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                 //msg == target 的情況隻能是屏障消息,即調用postSyncBarrier()方法
                //如果存在屏障,停止同步消息,異步消息還可以執行
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());  //找出異步消息,如果有的話
            }
            if (msg != null) {
                if (now < msg.when) {
                    //目前消息還沒準備好(時間沒到)
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 消息已準備,可以取出
                    if (prevMsg != null) {
                        //有屏障,prevMsg 為異步消息 msg 的前一節點,相當于拿出 msg ,連結前後節點
                        prevMsg.next = msg.next;
                    } else {
                        //沒有屏障,msg 即頭節點,将 mMessages 設為新的頭結點
                        mMessages = msg.next;
                    }
                    msg.next = null;  //斷開即将執行的 msg
                    msg.markInUse(); //标記為使用狀态
                    return msg;  //傳回取出的消息,交給Looper處理
                }
            } 
            // Process the quit message now that all pending messages have been ha
            if (mQuitting) {
                //隊列已經退出
                dispose();
                return null;  //傳回null後Looper.loop()方法也會結束循環
            }
    }
}
           

源碼中可以發現,雖然 MessageQueue 叫消息隊列,但卻是使用了連結清單的資料結構來存儲消息。 next()方法會從連結清單的頭結點開始,先看看頭結點是不是消息屏障(ViewRootImpl使用了這個機制),如果是,那麼就停止同步消息的讀取,異步消息照常運作。

源碼中可以發現,雖然 MessageQueue 叫消息隊列,但卻是使用了連結清單的資料結構來存儲消息。 next()方法會從連結清單的頭結點開始,先看看頭結點是不是消息屏障(ViewRootImpl使用了這個機制),如果是,那麼就停止同步消息的讀取,異步消息照常運作。

到這裡,我們就大緻能理清 Handler.handleMessage()方法是怎麼調起來的了。但是MessageQueue裡面的消息是怎麼來的呢?這個其實不看源碼也能猜出來了,肯定是由我們發送的消息那裡傳過來的,但是為了了解更深刻,還是得看看消息是怎麼傳遞到消息隊列中的(MessageQueue );

//Handler.java
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

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

可以看到,sendMessage()方法最終是調用了 sendMessageAtTime()方法,分析下這個方法,首先将會拿到一個消息隊列 mQueue,這個隊列是在建立 Looper的時候預設初始化的,然後會調用enqueueMessage()方法進隊:

//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
           

這個進隊方法裡面會将msg.target設為目前Handler,也就是上面說到的 Looper.loop()方法内最終調用的msg.target.dispatchMessage(msg)的這個 msg 的 target 來源。

如果目前 Handler 是異步的話,還會将發送的消息置為同步消息,這個 mAsynchronous 辨別是我們構造 Handler 的時候傳遞的參數,預設為 false。

最後就是真正的進隊方法 MessageQueue.enqueueMessage :

//MessageQueue.java  删減部分代碼
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) {
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;  //指派調用時間
        Message p = mMessages;  //頭結點
        if (p == null || when == 0 || when < p.when) {
            //隊列中沒有消息 或者 時間為0 或者 比頭結點的時間早
            //插入到頭結點中
            msg.next = p;
            mMessages = msg;
        } else {
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {  //類似插入排序,找到合适的位置
                    break;
                }
            }
            // 結點插入
            msg.next = p; 
            prev.next = msg;
        }
    }
    return true;
}
           

剛開始會進行一系列的判斷,然後根據時間來作為一個排隊依據進行進隊操作,需要注意的是:消息隊列是使用連結清單作為資料的存儲結構,是可以插隊的,即不存在發送了延時消息不會阻塞消息隊列。

再跟上面的出隊方法聯系起來,就會發現,異步消息并不會立刻執行,而是根據時間,完全跟同步消息一樣的順序插入隊列中。異步消息與同步消息唯一的差別就是當有消息屏障時,異步消息還可以執行,而同步消息則不行。

整個Handler的大體運作機制到此應該有了一個比較清晰的輪廓了。

總結一下:Handler 發送的線程不處理消息,隻有Looper.loop()将消息取出來後再進行處理,是以在Handler機制中,無論發送消息的Handler對象處于什麼線程,最終的處理都是運作在 Looper.loop() 所在的線程。

比如:一個新的線程 Thread1 發送了一個消息 Msg1,這個線程的工作僅僅是将消息存儲到消息隊列而已,并沒有下一步了,然後等待 Looper.loop() 處理到 Msg1 的時候(loop()方法一直運作在最開始調用它的線程,比如主線程),再将 Msg1 進行處理,是以最終就從 Thread1 切換到了主線程中運作。

可以拉到下面3.7小節看下流程圖,更清晰一些

3.2 Handler.post(Runnable) 方法是運作在新的線程嗎?

Handler 中發送消息的方法多達十幾個,分為 sendXXX 以及 postXXX ,這裡看看主要的幾個 post 類型方法:

//Handler.java 
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

...
           

幾個 post 方法都是調用了相應的sendXXX 方法,然後用getPostMessage(Runnable r) 建構 Message 對象:

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

這裡擷取到消息後,将 Runnable指派給 Message.callback ,那這個 callback 有什麼用呢?上面的整體流程分析中,我們知道 Looper.loop()會調用 msg.target.dispatchMessage(msg),這個target 就是 Handler 了,那麼看一下這個方法的具體實作:

// Handler.java 
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

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

到這一步終于水落石出了,如果是用 postXXX 方法發送的消息,就會調用 handleCallback(msg) 方法,即調用我們post方法裡傳遞的 Runnable 對象的run()方法。

也就是說,Runnable 跟線程沒有半毛錢關系,他隻是一個回調方法而已,隻不過我們平時建立線程的時候使用多了,誤以為他跟線程有什麼py交易。

3.3 Handler(Callback) 跟 Handler() 這兩個構造方法的差別在哪?

接着看3.2講到的 dispatchMessage() 方法剩下的邏輯。

如果 msg 沒有 callback 的話,那麼将會判斷 mCallback 是否為空,這個 mCallback 就是構造方法種傳遞的那個 Callback ,如果 mCallback為空,那麼就調用 Handler 的 handleMessage(msg) 方法,否則就調用 mCallback.handleMessage(msg) 方法,然後根據 mCallback.handleMessage(msg)的傳回值判斷是否攔截消息,如果攔截(傳回 true),則結束,否則還會調用 Handler#handleMessage(msg)方法。

也就是說:**Callback.handleMessage() 的優先級比 Handler.handleMessage()要高 。**如果存在Callback,并且Callback#handleMessage() 傳回了 true ,那麼Handler#handleMessage()将不會調用。

除了這點,還有什麼差別嗎?暫時真沒發現。

3.4 子線程可以建立 Handler 嗎?

問題可能有些模糊,意思是可以在子線程回調 handleMessage()嗎。

上面理清了 Handler 的運作流程,但是建立流程好像還沒怎麼說,先看看 Handler 是怎麼建立的:

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

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

public Handler(boolean async) {
    this(null, async);
}

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 " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
           

先看上面這部分不傳 Looper 的構造方法,這些方法最終都是調用了Handler(Callback callback, boolean async) 方法,是以直接看這個方法就行,一開始會在方法體内檢測是否有潛在的記憶體洩漏風險,相信大家都有過被這東西煩過,看圖:

百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

這種被黃色支配的感覺不太舒服,可以在執行個體上面添加注解@SuppressLint(“HandlerLeak”)來去掉提示,但是這隻是去掉提示而已,别忘了處理潛在的記憶體洩漏。

接着看下面,首先會調用 Looper.myLooper()方法拿到目前線程的 Looper 執行個體,如果為空,則抛異常,看看myLooper()具體是怎樣的:

//Looper.java
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
           

直接就是調用了 sThreadLocal的 get 方法,這個sThreadLocal是一個靜态的 ThreadLocal 常量,看名字就能猜到與線程相關,具體的就不深究了。可以先把他看成一個線程id 與 Looper 的 map 鍵值對。既然有 get() ,那麼就應該有 set() ,那麼 Looper 是在哪裡被存進去的呢?

//Looper.java
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.prepare() 方法中被傳進去的,并且 sThreadLocal 中每個線程都隻能有一個 Looper 執行個體。需要注意的是,prepare()方法并沒有調用 Looper#loop()方法,經過上面的流程分析也知道,這個 loop() 方法啟動才能處理發送的消息,是以子線程建立 Handler 除了需要調用 Looper.prepare()外,還需要調用 Looper.loop()啟動。

也就說明,任何線程都可以建立 Handler,隻要目前線程調用了 Looper.prepare()方法,那麼就可以使用 Handler 了,而且同一線程内就算建立 n 個 Handler 執行個體,也隻對應一個 Looper,即對應一個消息隊列。

理一理邏輯:Handler 機制要求建立 Handler 的線程必須先調用 Looper.prepare() 方法來初始化,初始化過程中會将目前線程的 Looper 存起來,如果沒有進行 Looper 的初始化,将會抛異常,要啟動 Looper ,還需要調用 loop() 方法。

3.5 為什麼主線程不用調用 Looper.prepare() ?

上面說了,每個線程要建立 Handler 就必須要調用 Looper.prepare進行初始化,那麼為什麼我們平時在主線程建立 Handler 則不需要調用?

通過3.1 中的 debug 調用鍊就可以知道,主線程的 loop()方法是在 ActivityThread#main()方法中被調用的,那麼看看 main() 方法:

//ActivityThread.java 删減部分代碼
public static void main(String[] args) {
    Looper.prepareMainLooper();
    Looper.loop();
}
           

到這裡就能明白了,在App啟動的時候系統預設啟動了一個主線程的 Looper,prepareMainLooper()也是調用了 prepare()方法,裡面會建立一個不可退出的 Looper,并 set 到 sThreadLocal對象當中。

3.6 為什麼建立 Message 對象推薦使用 Message.obtain()擷取?

Message 對象有兩種方式可以獲得,一種是直接 new 一個執行個體,另一種就是調用 Message.obtain()方法了,Handler.obtainMessage() 也是調用Message.obtain()實作的,看看這個方法:

//Message.java

private static Message sPool;

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

可以看到,如果消息池不為空,obtain() 方法會将頭結點 sPool取出,并置為非使用狀态,然後傳回,如果消息池為空,則建立一個消息。

知道有消息池這個東西了,那麼這個消息池的消息是怎麼來的呢?

使用 AS 搜尋一下,發現隻有兩個方法對 sPool 這個節點進行了指派,一個是上面的 obtain(),另一個是下面這個:

//Message.java

private static final int MAX_POOL_SIZE = 50;

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 對象的各種參數清空,如果消息池的數量小于最大數量(50)的話,就目前消息插入緩存池的頭結點中。

已經知道 Message 是會被回收的了,那麼什麼情況才會被回收呢?

繼續檢視調用鍊:

// Looper.java ,省略部分代碼
loop(){
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block  , 從隊列取出一個msg
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg); //Handler處理消息
        ...
        msg.recycleUnchecked();  //回收msg
    }
}
           

其中的一個調用是在 Looper.loop()方法中,調用時機是在 Handler 處理事件之後,既然是 Handler 處理後就會回收,那麼如果在 Handler.handleMessage() 中用新的線程使用這個 msg 會怎樣呢?

//MainActivity.java
@SuppressLint("HandlerLeak")
static Handler innerHandler = new Handler() {
    @Override
    public void handleMessage(final Message msg) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean isRecycle = msg.obj == null;
                Log.e("====是否已經回收===", "" + isRecycle);
            }
        }).start();
    }
};

private void send(){
    Message msg = Message.obtain();
    msg.what = 0; //辨別
    msg.obj = "這是消息體"; //消息内容
    innerHandler.sendMessage(msg);
}
           

當調用 send()方法發送消息後,發現打出 log:

E/====是否已經回收===: true
           

也就說明我們的推斷是正确的。是以在平時使用中,不要在 handleMessage(Message msg)方法中對 msg 進行異步處理,因為異步處理後,該方法會馬上傳回,相當于告訴 Looper 已經處理完成了,Looper 就會将其回收。

如果真要在異步中使用,那麼可以建立一個新的 Message 對象,并将值指派過去。

回到前面的問題,我們目前發現了一個 Message 被回收的地方,那麼其他地方有調用這個 Message .recycleUnchecked() 嗎?接着看看:

//Message.java
public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
           

Message 還有一個公共的回收方法,就是上面這個了,我們可以手動調用這個進行回收。還有就是消息隊列中各種 removeMessage 也會觸發回收,調用鍊太多了,就不貼代碼了。

總而言之,因為 Handler 機制在整個 Android 系統中使用太頻繁,是以 Android 就采用了一個緩存政策。就是 Message 裡面會緩存一個靜态的消息池,當消息被處理或者移除的時候就會被回收到消息池,是以推薦使用 Message.obtain()來擷取消息對象。

3.7 梳理

到此就把Handler的大緻流程分析完了,再畫個圖重新梳理一下思路:

百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

把整個Handler機制比作一個流水線的話,那麼 Handler 就是勞工,可以在不同線程傳遞 Message到傳送帶(MessageQueue),而傳送帶是被馬達(Looper)運輸的,馬達又是一開始就運作了(Looper.loop()),并且隻會在一開始的線程,是以無論哪個勞工(Handler)在哪裡(任意線程)傳遞産品(Message),都隻會在一條傳送帶(MessageQueue)上被唯一的馬達(Looper)運送到終點處理,即 Message 隻會在調用 Looper.loop() 的線程被處理。

4 常見問題&技巧

4.1 為什麼 Handler 會造成記憶體洩漏?

先來回顧下基礎知識,可能造成記憶體洩漏的原因可以大緻概括如下:

生命周期長的對象引用了生命周期短的對象。

Handler 跟其他一些類一樣,本身是不會造成記憶體洩漏的,Handler 造成記憶體洩漏的一般原因都是由于匿名内部類引起的,因為匿名内部類隐性地持有外部類的引用(如果不持有引用怎麼可以使用外部類的變量方法呢?)。

是以當内部類的生命周期比較長,如跑一個新的線程,碰巧又碰到生命周期短的對象(如Activity)需要回收,就會導緻生命周期短的對象還在被生命周期長的對象所引用,進而回收不了。

典型的例子:

public class Main {

    int _10m = 10*1024*1024;
    byte[] bytes = new byte[4*_10m];

    public void run() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static void main(String args[]) {
        Main object = new Main();
        object.run();
        object =null;
        System.gc();
    }
}
           

輸出log:

[GC (System.gc()) [PSYoungGen: 3341K->880K(38400K)] 44301K->41848K(125952K)

[Full GC (System.gc()) [PSYoungGen: 880K->0K(38400K)] [ParOldGen: 40968K->41697K(87552K)] 41848K->41697K(125952K)

可以看到,即使object引用為空,object 對象還是沒有被回收。這就會發生了記憶體洩漏,如果出現很多次這樣的情況,那麼就很有可能發生記憶體溢出(OutOfMemery)。

在 Handler 裡面其實是類似的道理,匿名内部類的 Handler 持有 Activity 的引用,而發送的 Message 又持有 Handler 的引用,Message 又存在于 MessageQueue 中,而 MessageQueue 又是 Looper 的成員變量,并且 Looper 對象又是存在于靜态常量 sThreadLocal 中。

是以反推回來,因為 sThreadLocal 是方法區常量,是以不會被回收,而 sThreadLocal 又持有 Looper 的引用…balabala…還是看圖吧:

百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

即 sThreadLocal 間接的持有了 Activity 的引用,當 Handler 發送的消息還沒有被處理完畢時,比如延時消息,而 Activity 又被使用者傳回了,即 onDestroy() 後,系統想要對 Activity 對象進行回收,但是發現還有引用鍊存在,回收不了,就造成了記憶體洩漏。

4.2 怎麼防止 Handler 記憶體洩漏?

從上面的分析中,可以知道,想要防止 Handler 記憶體洩漏,一種方法是把 sThreadLocal 到 Activity 的引用鍊斷開就行了。

最簡單的方法就是在 onPause()中使用 Handler 的 removeCallbacksAndMessages(null)方法清除所有消息及回調。就可以把引用鍊斷開了。

Android 源碼中這種方式也很常見,不在 onDestroy()裡面調用主要是 onDestroy() 方法不能保證每次都能執行到。

第二種方法就是使用靜态類加弱引用的方式:

public class MainActivity extends AppCompatActivity {

    public TextView textView;

    static class WeakRefHandler extends Handler {

        //弱引用
        private WeakReference<MainActivity> reference;

        public WeakRefHandler(MainActivity mainActivity) {
            this.reference = new WeakReference<MainActivity>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = reference.get();
            if (activity != null) {
                activity.textView.setText("雞湯程式員");
            }
        }
    }
}
           

因為靜态類不會持有外部類的引用,是以需要傳一個 Activity 過來,并且使用一個弱引用來引用 Activity 的執行個體,弱引用在 gc 的時候會被回收,是以也就相當于把強引用鍊給斷了,自然也就沒有記憶體洩漏了。

4.3 Loop.loop() 為什麼不會造成應用卡死?

上面也提了這個問題,按照一般的想法來說,loop() 方法是一個死循環,那麼肯定會占用大量的 cpu 而導緻應用卡頓,甚至說 ANR 。

但是 Android 中即使使用大量的 Looper ,也不會造成這種問題,問什麼呢?

由于這個問題涉及到的知識比較深,主要是通過 Linux 的 epoll 機制實作的,這裡需要 Linux 、 jni 等知識,我等菜鳥就不分析了,大家可以去搜這類似的文章了解下。

5. 總結

以上就是篇文章的全部分析了,這裡總結一下:

  1. Handler 的回調方法是在 Looper.loop()所調用的線程進行的;
  2. Handler 的建立需要先調用 Looper.prepare() ,然後再手動調用 loop()方法開啟循環;
  3. App 啟動時會在ActivityThread.main()方法中建立主線程的 Looper ,并開啟循環,是以主線程使用 Handler 不用調用第2點的邏輯;
  4. 延時消息并不會阻塞消息隊列;
  5. 異步消息不會馬上執行,插入隊列的方式跟同步消息一樣,唯一的差別是當有消息屏障時,異步消息可以繼續執行,同步消息則不行;
  6. Callback.handleMessage() 的優先級比 Handler.handleMessage()要高*
  7. Handler.post(Runnable)傳遞的 Runnale 對象并不會在新的線程執行;
  8. Message 的建立推薦使用 Message.obtain() 來擷取,内部采用緩存消息池實作;
  9. 不要在 handleMessage()中對消息進行異步處理;
  10. 可以通過removeCallbacksAndMessages(null)或者靜态類加弱引用的方式防止記憶體洩漏;
  11. Looper.loop()不會造成應用卡死,裡面使用了 Linux 的 epoll 機制。

6. APP開發架構體系,Android架構師腦圖,全套視訊

  • 6.1 APP開發架構體系;
    百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!
  • 6.2 BAT主流Android進階架構技術大綱+學習路線+資料分享
  • 阿裡P8級Android架構師技術腦圖
    百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!
  • 全套體系化進階架構視訊;七大主流技術子產品,視訊+源碼+筆記
    百度阿裡騰訊頭條面試Android進階崗必問!Handler源碼解析!

讀者福利

Android架構師的門檻,有沒有免費學習資料?

有Android開發3-5年基礎,希望突破瓶頸,成為架構師的小夥伴,可以加入Android進階架構群;1007478004,免費提供視訊和資料,一起學習,互相讨論。