理解 Android 消息機制

本人只是Android小菜一個,寫技術文章只是爲了總結本身最近學習到的知識,歷來不敢爲人師,若是裏面有不正確的地方請你們盡情指出,謝謝!java

本文基於原生 Android 9.0 源碼來解析 Android 消息機制:

frameworks/base/core/java/android/os/Handler.java
frameworks/base/core/java/android/os/Looper.java
frameworks/base/core/java/android/os/MessageQueue.java
frameworks/base/core/java/android/os/Message.java
frameworks/base/core/java/android/app/ActivityThread.java
複製代碼

1. 概述

咱們知道在Android的主線程中不能進行耗時操做,例如網絡訪問、數據處理等,由於一旦主線程的任務處理時間超過系統規定的限制就會出現應用不響應的狀況。但在實際工做中,處理耗時任務是不可避免的,並且常常須要在處理完耗時任務後更新某些UI控件,以顯示處理結果。在這種場景下,最經常使用方案就是在新線程中進行耗時操做,處理完成後通知主線程進行相關UI的更新,這時就須要使用到Android消息機制了。android

其實在前面幾篇文章中,小菜講解過IntentServiceAsyncTaskHandlerThread的使用方法和實現原理,它們都是Android消息機制的具體應用。緩存

到底什麼是消息機制呢?簡單來講,Android消息機制是一套以「消息」爲中介來實現線程之間的任務切換或同一線程中任務的按需執行的機制,其中涉及到消息的發送、存儲消息、消息循環以及消息的分發和處理。markdown

本文將先經過一個簡單的示例演示如何使用Android消息機制,再經過分析源碼來進一步瞭解消息機制的內部實現方式,最後會講解一些使用Android消息機制的注意點。網絡

2. 初見 Android 消息機制

先用一個簡單示例來展現下Android消息機制在實際工做中如何使用,就直接利用前面提到的場景,即子線程處理耗時任務並在任務處理完畢後通知主線程進行UI的更新,示例代碼以下:併發

public class MainActivity extends Activity {
    // 定義 Handler 和 Thread 局部變量
    private Handler mHandler;
    private Thread mThread;

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

        mHandler = new Handler() {
            @Override
            public void handleMessage (Message msg) {
                // 4. 根據不一樣的消息類型進行不一樣的處理
                switch (msg.what) {
                    // 在這裏進行和 UI 相關的操做,例如顯示處理結果。
                }
            }
        };

        mThread = new Thread() {
            @Override
            public void run() {
                // 2. 休眠一段時間,模擬子線程在處理耗時任務。
                try {
                    Thread.sleep(30000);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                // 3. 發送消息
                mHandler.sendEmptyMessage(0);
            }
        };

        // 1. 開啓子線程
        mThread.start();
    }
}
複製代碼

小菜在示例代碼裏經過序號標註了邏輯流程,即先開啓子線程並在線程內部處理任務,任務處理完成後經過Handler向主線程發送消息,最後在主線程中處理消息並更新UI。app

看起來Android消息機制很簡單嘛,只要利用Handler發送消息並處理其中的消息就能夠了嘛。真的這麼簡單嗎?固然不是!前面提到過在消息機制中涉及到幾個關鍵點:發送消息、存儲消息、消息循環和分發處理消息,在這個示例中咱們只看到了發送消息和處理消息,並無看到存儲消息和消息循環。框架

這是由於這個例子中的Handler使用的消息是發送和存儲在主線程中的消息隊列中,這個消息隊列的建立和循環都是在主線程建立的時候系統自動進行的,對咱們是透明的,不利於理解消息機制的總體流程。less

如今給出一個更爲通用的示例,從這個例子中能夠清楚地看到消息隊列的建立和消息循環的開啓:async

class LooperThread extends Thread {
    public Handler mHandler;
    
    public void run() {
        // 初始化 Looper 對象,其內部會建立消息隊列。
        Looper.prepare();
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
            // 處理消息隊列中的消息。
            }
        };
        // 開啓消息循環,會從消息隊列中取出消息,沒有消息時等待新消息的到來。
        Looper.loop();
    }
}
複製代碼

綜合這兩個示例,咱們瞭解了Android消息機制的使用方法,也看到了發送消息、建立消息隊列、開啓消息循環以及處理消息的過程,下面給出一個更直觀的「消息傳遞流程圖」:

經過流程圖能夠看到整個消息傳遞過程,也能夠看到在不一樣的階段涉及的類:

  • 消息發送:經過 Handler向關聯的MessageQueue發送消息;
  • 消息存儲: 把發送的消息以Message的形式存儲在MessageQueue中;
  • 消息循環:經過Looper不停地從MessageQueue中獲取消息,隊列中沒有消息時就阻塞等待新消息;
  • 消息分發和處理:Looper獲取消息後分發給Handler進行處理。

3. 理解 Android 消息機制

前面提到消息傳遞流程主要分爲「發送消息」、「存儲消息」、「消息循環」和「消息分發和處理」幾個不一樣階段,小菜本打算按照這個流程來分別講解每一個階段,可是在具體行文的時候發現每一個階段並非徹底分割開來的,好比在講「發送消息」以前要先了解「消息的存儲結構」和「消息循環的開啓」,而「消息的分發」又是屬於「消息循環」的功能。

正是因爲這幾個階段之間的相互關係,致使沒有辦法嚴格按照消息傳遞的順序講解Android消息機制。思慮再三,小菜決定經過上面講解的Android消息機制通用示例來一步步解析其背後的邏輯流程。

再來看下通用示例:

class LooperThread extends Thread {
    public Handler mHandler;
    
    public void run() {
        // 1. 初始化 Looper 對象,其內部會建立消息隊列。
        Looper.prepare();
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
            // 4. 處理消息隊列中的消息。
            }
        };
        // 2. 開啓消息循環,會從消息隊列中取出消息,沒有消息時阻塞等待新消息的到來。
        Looper.loop();
    }
    
    // 3. 發送消息
    mHandler.sendEmptyMessage(0);
}
複製代碼

在示例代碼中用不一樣的序號標註了「消息傳遞機制」的各個關鍵點,如下的內容也都是根據這些關鍵節點進行講解的。

3.1 消息載體

「消息」是Android消息機制中信息的載體,它包含了在整個消息傳遞過程當中想要傳送的數據,要理解「消息機制」就要先了解這個消息載體類Message:

/** * Defines a message containing a description and arbitrary data object that can be * sent to a {@link Handler}. This object contains two extra int fields and an * extra object field that allow you to not do allocations in many cases. * * <p class="note">While the constructor of Message is public, the best way to get * one of these is to call {@link #obtain Message.obtain()} or one of the * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull * them from a pool of recycled objects.</p> */
public final class Message implements Parcelable { ... }
複製代碼

Android框架中對消息載體Message的聲明雖然簡短,卻傳達了最核心最重要的兩點信息:

  1. Message的做用:Message是包含了描述信息和數據對象而且在消息機制中發送給Handler的對象,其中的數據對象主要包括兩個整型域和一個對象域,經過這些域能夠傳遞信息。整型的代價是最小的,因此儘可能使用整型域傳遞信息。
/** * User-defined message code so that the recipient can identify * what this message is about. Each {@link Handler} has its own name-space * for message codes, so you do not need to worry about yours conflicting * with other handlers. */
public int what;

/** * arg1 and arg2 are lower-cost alternatives to using * {@link #setData(Bundle) setData()} if you only need to store a * few integer values. */
public int arg1;
public int arg2;

/** * An arbitrary object to send to the recipient. When using * {@link Messenger} to send the message across processes this can only * be non-null if it contains a Parcelable of a framework class (not one * implemented by the application). For other data transfer use * {@link #setData}. * * <p>Note that Parcelable objects here are not supported prior to * the {@link android.os.Build.VERSION_CODES#FROYO} release. */
public Object obj;
複製代碼
  1. Message的建立方式:雖然Message有公有構造函數,可是建議使用其提供的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) {
        // 緩存池中存在可用對象時去緩存池獲取 Message 對象。
        if (sPool != null) {
            // 獲取緩存中的對象,並把緩存池指針後移。 
            Message m = sPool;
            sPool = m.next;
            
            m.next = null;
            // 清除標誌位
            m.flags = 0; // clear in-use flag
            // 更新當前緩存池大小
            sPoolSize--;
            return m;
        }
    }
    // 緩存池中沒有可用對象時直接建立一個新的 Message 對象。
    return new Message();
}
複製代碼

Message中有一系列obtain函數用以在不一樣場景中獲取對象,但這個是最核心的,其餘函數都會在其內部調用它,有興趣的同窗能夠自行查看源碼,考慮到篇幅問題,這裏就再也不一一列舉說明了。

看到這裏,相信你們都會有一個疑問:obtain函數是從緩存池中獲取Message對象,那緩存池中的對象是何時被添加進去的呢?既然緩存池中的對象都是一些能夠被重複使用的對象,很明顯是在Message對象再也不被須要的時候,即從MessageQueue中取出並分發給Handler的時候,被添加到緩存中的,使用的是recycleUnchecked函數:

/** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */
void recycleUnchecked() {
    // 設置標誌位爲「使用中」,在從緩存中取出時會清除這個標誌位。
    flags = FLAG_IN_USE;
    // Message 對象中的信息都再也不有意義,在放入緩存池前直接清空。
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        // 緩存池中只緩存必定數量的 Message 對象,默認是 50 個。
        if (sPoolSize < MAX_POOL_SIZE) {
            // 把對象放在緩存池的鏈表首部。 
            next = sPool;
            sPool = this;
            // 及時更新緩存池大小。
            sPoolSize++;
        }
    }
}
複製代碼

3.2 建立消息隊列

消息隊列的建立對消息傳遞相當重要,它決定了消息在傳遞過程當中的存取方式。可是線程在默認狀況下是沒有消息隊列的,也沒法在其內部進行消息循環。若是想爲線程開啓消息循環就須要使用到Looper類,它能夠爲關聯的線程建立消息隊列並開啓消息循環,建立消息隊列的方式是調用prepare()接口:

/** * Class used to run a message loop for a thread. Threads by default do * not have a message loop associated with them; to create one, call * {@link #prepare} in the thread that is to run the loop, and then * {@link #loop} to have it process messages until the loop is stopped. */
public final class Looper {
    // 省略無關代碼
    
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    // 內部的消息隊列和關聯的線程
    final MessageQueue mQueue;
    final Thread mThread;
    
    // 省略無關代碼
    
    /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */
    public static void prepare() {
        // 建立可退出的消息循環,主線程的消息循環是不可退出的。
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        // 若是當前線程已經有了 Looper 對象就直接拋出異常,
        // 由於一個線程只能有一個消息隊列。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 建立 Looper 對象並和線程關聯。
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    // 私有構造函數,建立消息隊列並獲取當前線程對象。
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
複製代碼

能夠看到Looper.prepare()只是在內部建立了一個MessageQueue對象並和當前線程關聯起來,同時還保證了每一個線程只能有一個消息隊列。

很顯然MessageQueue就是用來存儲消息對象的結構了,看下它的聲明:

/** * Low-level class holding the list of messages to be dispatched by a * {@link Looper}. Messages are not added directly to a MessageQueue, * but rather through {@link Handler} objects associated with the Looper. * * <p>You can retrieve the MessageQueue for the current thread with * {@link Looper#myQueue() Looper.myQueue()}. */
public final class MessageQueue { ... }
複製代碼

MessageQueue是一個持有消息對象列表的類,而這些消息對象經過和Looper關聯的Handler添加並最終由Looper進行分發,其中有個關鍵信息須要引發咱們的格外關注:list of messages,這是否是告訴咱們雖然這個類的名字是queue可是其內部並非隊列而是列表呢?確實如此,MessageQueue的內部是使用單向鏈表的方法進行存取的,這點在後面解析Message的存取過程當中會看到,在這裏就不詳細講述了。

3.3 開啓消息循環

「消息隊列」建立完成了,是否是就能夠直接向其中添加消息對象了呢?還不到時候,還須要先開啓消息循環,來監聽消息隊列的狀況,這時須要使用Looper.loop()接口:

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */
public static void loop() {
    // 獲取當前線程的 Looper 對象,獲取失敗時拋出異常。
    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
        if (msg == null) {
            // 消息隊列正在退出時就終止監聽並退出循環
            return;
        }

        // 省略無關代碼
        
        try {
            // 分發消息,把消息發送合適的處理對象。
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        // 省略無關代碼
        
        // 回收消息對象,放入消息緩存池中以待後續複用。
        msg.recycleUnchecked();
    }
}
複製代碼

這段代碼自己比較複雜,小菜省略了其中和核心邏輯無關的部分代碼,以方便你們閱讀和理解,其核心邏輯就是利用一個「無限循環」來監聽消息隊列,當發現有可用消息就取出並分發處理,若是沒有就一直等待。

3.4 發送和存儲消息

「消息隊列」已經建立完成,「消息循環」也已經開啓,終於可用發送消息了。

要發送消息,就要使用到Handler類了,其中的sendpost系列方法均可以進行「消息的發送」,核心方法都是同樣的,在這裏就以post方法來說解下發送消息的過程:

/** * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */
public final boolean post(Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}
    
private static Message getPostMessage(Runnable r) {
    // 把 Runnable 對象封裝成 Message 並設置 callback,
    // 這個 callback 會在後面消息的分發處理中起到做用。
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
    
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) {
    // 消息隊列,即經過 Looper.prepare() 建立的消息隊列。
    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);
}
    
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 設置消息隊列的目標,用於後續的消息分發過程。
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 消息對象入隊
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼

經過一系列的調用過程,Handler最終會經過 MessageQueue.enqueueMessage()把消息存儲到消息隊列中,MessageQueue內部又是如何存儲這個發送過來的消息對象的呢?

boolean enqueueMessage(Message msg, long when) {
    // 消息對象的目標是 null 時直接拋出異常,由於這意味這個消息沒法進行分發處理,
    // 是不合法的消息對象。
    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;
        }

        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;
}
複製代碼

3.5 消息分發處理

當消息隊列中有新的消息而且消息循環被喚醒後,消息隊列中的消息就能夠被取出並分發給合適的處理者了,這點能夠在「開啓消息循環」一節中看到,利用的是msg.target.dispatchMessage(msg),而target就是Handler對象,直接看具體的分發過程:

public void dispatchMessage(Message msg) {
    // Message 對象是從 Runnable 封裝造成的時候,callback 不爲空。
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // mCallback 是在 Handler 的構造函數中設置的,也能夠不設置。
        if (mCallback != null) {
            // 調用 Handler 的 callback 處理消息
            if (mCallback.handleMessage(msg)) {
                // 能夠攔截消息,以後 Handler.handleMessage 將沒法繼續處理這個消息。
                return;
            }
        }
        // 調用 Handler 的 handleMessage 處理消息,子類會實現這個方法。
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    // Message 中的 callback 是 Runnable,直接執行 Runnable.run()。
    message.callback.run();
}

/** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. */
 public interface Callback {
    /** * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */
    // Handler 的回調方法,經過返回值能夠進行消息攔截。
    public boolean handleMessage(Message msg);
}
    
/** * Subclasses must implement this to receive messages. */
// Handler 的處理消息回調,子類須要實現。
public void handleMessage(Message msg) {
}
複製代碼

消息的分發是有必定優先順序的:

  1. 首先會考慮交給Message.callback來處理,若是是經過post系列函數發送的消息會走到這裏進行處理,而經過send系列函數發送的消息默認是沒有這個回調接口的;
  2. 若是Message.callback不存在就考慮交給Handler.callback來處理,在處理過程當中能夠經過返回值攔截消息;
  3. 若是Handler.callback不存在或者存在可是在處理消息過程當中沒有進行攔截,就會交給Handler.handleMessage來處理,這個接口須要子類實現,也是在實際工做中最經常使用的處理消息的地方。

到這裏,消息的傳遞過程就基本講完了,你們能夠結合以前的流程圖仔細揣摩,相信能夠對Android消息機制有更深入的理解。

4. 延伸知識點

4.1 主線程消息循環的建立

前面講到一個線程默認是沒有消息隊列的,也沒法在其內部開啓消息循環,可是咱們在實際工做中常常會直接在主線程中使用Handler來進行消息的發送和處理,而且運行正常,這是由於主線程在啓動的時候就已經建立了消息隊列並開啓了消息循環,只是這個過程是透明的,咱們沒有感知到。

瞭解Activity啓動過程的同窗應該已經想到了這個建立過程是在哪裏了,沒錯,就是在ActivityThread,不瞭解啓動過程的同窗也不要擔憂,後續我會講解具體的啓動過程。在這裏,你們只要簡單地把ActivityThread當作Activity的啓動入口便可,直接來看入口函數:

/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. * * {@hide} */
public final class ActivityThread extends ClientTransactionHandler {
    public static void main(String[] args) {
        // 記錄開始,用於後續經過 systrace 檢查和調試性能問題。
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        // 省略無關代碼

        // 爲主線程建立消息隊列
        Looper.prepareMainLooper();

        // 省略無關代碼
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // 記錄結束,後續能夠經過 systrace 觀察這段代碼的執行狀況。
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        
        // 開啓消息循環
        Looper.loop();

        // 主線程消息循環不會退出,若是走到這意味着發生意外,拋出異常。
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}
複製代碼

代碼結構和Android消息機制的通用示例很像,在裏面看到了消息隊列的建立和消息循環的開啓,不一樣之處在於主線程中建立消息隊列使用的是Looper.prepareMainLooper

/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */
public static void prepareMainLooper() {
    // 啓動一個沒法退出的消息循環
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        // 返回主線程 looper 對象
        sMainLooper = myLooper();
    }
}
複製代碼

爲主線程建立的消息循環是沒法退出的,由於這個消息循環要處理不少重要事務,好比Activity生命週期的回調等,若是退出將致使異常,這點在後續講解Activity啓動過程的時候再詳細解析。

4.2 內存泄露

Java垃圾回收機制對於每一個從事Java的開發者應該都不陌生,咱們也清楚並非全部對象佔用的內存均可以被及時回收,若是垃圾回收器準備回收某些對象,可是因爲它們還被其餘對象引用,那麼這些對象就沒法被回收,這也是內存泄漏的主要緣由。

使用Android消息機制時會不會致使內存泄漏呢?首先來看一種常見的使用方法:

public class MainActivity extends Activity {
    private TextView mTextView = null;
    private Handler mMyHandler = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化控件
        mTextView = (TextView) findViewById(R.id.sample_text);
        // 初始化 Handler 對象
        mMyHandler = new MyHandler();
        // 啓動一個延遲消息,在 3000ms 後有 mMyHandler 執行。
        mMyHandler.sendEmptyMessageDelayed(0, 3000);
    }

    private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // 執行消息,更新主線程中的控件。
            if (mTextView != null) {
                mTextView.setText("execute message");
            }
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
複製代碼

在這個示例中,MyHandler是以Activity內部類的形式存在的,因此mMyHandler是須要持有外部類對象引用的,而mMyHandler又被其發送的Message對象以target的方式引用,最終的結果就是Activity間接被Message引用。因爲這個Message須要在必定的延遲後被執行,若是在這以前Activity退出,可是因爲其引用被Message持有,致使沒法被系統回收,進而致使內存泄露。

既然ActivityMessage引用致使內存泄露,那有沒有辦法不讓其持有引用呢?固然能夠,使用「靜態內部類」就能夠避免這種狀況,由於「靜態內部類」不須要持有外部類對象的引用,來看示例代碼:

public class MainActivity extends Activity {
    private TextView mTextView = null;
    private Handler mMyHandler = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.sample_text);

        // 初始化 Handler 對象,並把主線程控件做爲參數傳入。
        mMyHandler = new MyHandler(mTextView);
        // 啓動一個延遲消息,在 3000ms 後有 mMyHandler 執行。
        mMyHandler.sendEmptyMessageDelayed(0, 3000);
    }

    private static class MyHandler extends Handler {
        // 經過弱引用的方式持有外部對象的變量。
        private WeakReference<TextView> mTextViewRef = null;

        // 初始化弱引用對象,此後就持有了正確的對象引用。
        public MyHandler(TextView textView) {
            mTextViewRef = new WeakReference<>(textView);
        }
        @Override
        public void handleMessage(Message msg) {
            // 執行消息,更新主線程中的控件。
            if (mTextViewRef != null && mTextViewRef.get() != null) {
                mTextViewRef.get().setText("execute message");
            }
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 退出時狀況消息隊列中的消息
        mMyHandler.removeCallbacksAndMessages(null);
    }
}
複製代碼

經過「靜態內部類」和「弱引用」的結合,既能夠不持有外部類對象引用又能夠訪問外部類對象的變量,並在Activity退出時又移除消息隊列中的消息,進一步避免了內存泄露的風險。

這只是其中一中避免內存泄露的方法,確定還有其餘方法也能夠達到目的,有興趣的同窗能夠自行研究。

5. 總結

本文講解了Android消息機制的使用方法、總體流程和每一個階段的實現原理,在最後還提到主線程消息循環的建立以及錯誤使用致使的內存泄漏及避免方法,但願能對你們學習消息機制有所幫忙。

相關文章
相關標籤/搜索