Android核心知識——消息機制

Handler簡介

Handler在平常開發中或多或少的都用過,在Android開發中是一個比較重要的知識點,但願經過這篇文章會使你對Handler有更全面的理解。java

Hanlder設計的初衷或用途主要有兩點:android

  • 在不一樣線程中執行任務。git

  • 執行定時任務。github

Handler基本使用方式

下面代碼展現了Handler使用的基本流程。面試

// 定義一個消息標識符
final int MESSAGE_WHAT_TEST = 100;

// 1.建立一個Handler對象。
Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 2. 重寫handleMessage方法處理髮送過來的Message

        // 判斷Message類型
        if(msg.what == MESSAGE_WHAT_TEST) {
            // 接收Message中的數據
            String data = (String) msg.obj;
            // 展現數據
            textView.setText(data);
        }
    }
};

// 3. 新建一個Message對象承載要傳輸的數據。
Message msg = new Message();
// 4. 給Message設置一個標識符,這個標識符能夠用來區分這個Message是用來幹什麼的。
msg.what = MESSAGE_WHAT_TEST;
msg.obj = "這裏能夠是任何類型的數據對象";
// 5. 將Message發送給Handler處理
handler.sendMessage(msg);
複製代碼

上面代碼展現了使用Handler的基本流程。可是編碼仍是存在一些缺陷。例如Handler可能會致使Activity內存泄漏、使用優先使用Message.obtain();而不是new Message();等。這些問題在後面你會找到答案。算法

這裏咱們Hanlder使用大體分爲三種狀況:數組

  • 子線程發送消息到主線程。安全

  • 主線程發送消息到子線程。bash

  • 執行定時任務、或週期性行任務。服務器

下面咱們結合實際案例或使用場景來看一下Handler是如何解決問題的。

子線程發送消息到主線程。

這種狀況的典型案例就是子線程請求數據,主線程更新UI。這也是最廣爲人知的使用場景了,由於Android系統推薦UI相關操做在主線程中進行。在ViewRootImpl.javacheckThread方法中進行了線程校驗。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}
複製代碼

因爲這一點限制,致使必須在主線程中進行UI操做。另外Android不推薦在主線程中作耗時操做,耗時操做會致使UI卡頓甚至程序沒法響應,也就是ANR

有同窗就有疑問了,爲何不在子線程中更新UI呢?

由於Android中的View不是線程安全的,多線程併發的操做UI可能會出現不可預期的狀態。子線程在特定狀況下也是能夠完成UI更新的,只不過不推薦這麼作。

有同窗又問了,那爲何不把View設計成線程安全的呢?

從設計上看,View即處理界面展現、又處理線程安全不符合單一職責原則。從效率上看,線程安全須要用到鎖機制會致使View的設計複雜,某些狀況會致使線程阻塞等問題。

還有同窗問。。。等會,先別問了,繼續日後看或許你的問題就有答案了。

由於有了上述的限制,在平常開發的過程當中你們都是採用開啓一個子線程來請求數據,而後使用Handler將數據發送到主線程,主線程收到數據後更新UI。

// 在主線程中建立一個Handler
final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 在主線程中執行
        // 接收網絡請求的數據
        String data = (String) msg.obj;
        // 更新UI,將數據展現到頁面上
        textView.setText(data);
    }
};

// 新建一個子線程請求數據
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            // 模擬網絡請求數據耗時
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 假設這是來自服務器的數據
        String data = "我是來自遠方的數據";
        // 新建一個Message對象來承載數據
        Message message = Message.obtain();
        // 設置一個flag來標識這個Message的類型
        message.arg1 = 1;
        // 將數據放入Message中
        message.obj = data;
        // 將Message發送到主線程中處理
        handler.sendMessage(message);
    }
}).start();
複製代碼

首先咱們在主線程中建立一個Handler,由於Handler是在主線程中建立的,因此handleMessage方法則會在主線程中執行。而後新建一個子線程(工做線程)去請求網絡數據,請求數據成功以後使用Message包裝數據,使用handler.sendMessage(message)將數據發送給Handler處理。以後在handleMessage方法中就會收到子線程的消息,而後將數據展現在頁面上。這樣就完成了子線程請求數據,主線程展現數據的需求了。

主線程發送消息到子線程。

這種狀況的實際應用場景不太好找,咱們就用簡單的代碼來講明一下這種狀況有哪些注意事項吧。

new Thread(new Runnable() {
    @Override
    public void run() {
        // 初始化子線程Looper
        Looper.prepare();
        // 在子線程中建立一個Handler對象。
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 重寫handleMessage方法處理髮送過來的Message

                // 判斷Message類型
                if (msg.what == MESSAGE_WHAT_TEST) {
                    // 接收Message中的數據
                    String data = (String) msg.obj;
                    Log.d(TAG, data);
                    // 能夠更新UI,可是強烈不推薦這麼作
                    // textView.setText(data);
                }
            }
        };
        // 開啓Looper,發現有消息就會交給handler處理
        Looper.loop();
    }
}).start();

// 點擊按鈕發送一條消息到子線程
sendMessageButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 新建一個Message對象承載要傳輸的數據。
        Message msg = Message.obtain();
        // 給Message設置一個標識符,這個標識符能夠用來區分這個Message是用來幹什麼的。
        msg.what = MESSAGE_WHAT_TEST;
        msg.obj = "這是來自主線程的數據";
        // 將Message發送給Handler處理
        handler.sendMessage(msg);
    }
});
複製代碼

主線程發送消息到子線程說明要在子線程中處理消息,因此Handler是在子線程中建立的。另外在子線程中處理消息要使用Looper.prepare();Looper.loop();建立並開啓消息循環機制,並在這兩行代碼之間完成Handler的建立和處理。這裏的編碼順序必定要牢記,不然消息是不會被handleMessage處理的。

在子線程使用handler,當handler再也不須要發送和處理消息須要使用Looper.myLooper().quit();Looper.myLooper().quitSafely();中止消息輪詢。由於Looper.loop()是一個死循環,若是不主動結束它會一直執行,子線程也就一直執行。

當咱們點擊sendMessageButton的時候就會向子線程發送一條消息。在子線程handleMessage中對消息進行了處理,能夠看到textView.setText(data);被註釋了,這句代碼是能夠正常運行的,收到的數據會展現到textView中,可是強烈不推薦這樣作。

執行定時任務、或週期性行任務。

Handler提供了延遲發送消息的API能夠用來實現定時任務或週期性任務。

handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);

        Log.d(TAG, "三秒打印一條日誌");
        // 延遲三秒發送一條空消息
        handler.sendEmptyMessageDelayed(0, 3 * 1000);
    }
};

// 發送第一條空消息
handler.sendEmptyMessage(0);
複製代碼

sendEmptyMessageDelayed第二參數指定了延遲時間,上面咱們設置延遲3000毫秒發送一條空消息,而後handler會收到這條空消息交給handleMessage處理,這樣sendEmptyMessageDelayed就會再次被執行,最終造成了定時執行的效果。

Handler使用注意事項

Handler致使內存泄漏問題

在最新版的Android Studio中編譯上面代碼時,編譯器會提示咱們This Handler class should be static or leaks might occur (anonymous android.os.Handler)編譯器建議咱們將Handler設置成靜態的內部類,不然可能會致使內存泄漏。

這是由於Java內部類會持有外部類的強引用,上面咱們建立Handler使用的都是匿名內部類的形式,因此Handler內部會持有外部類(Activity)的強引用,而後Message會持有handler的強引用,Message會被放到MessageQueue中等待被處理,若是這時Activity退出了可是Message尚未被處理就會致使Activity不能被GC釋放一直停留在內存中。也就造成了Activity的內存泄漏。

解決這個問題也很簡單,編譯器給出瞭解決建議。

// 將Handler聲明爲靜態的內部類
static class H extends Handler {
    // 使用WeakReference(弱引用)保存Activity引用。
    private WeakReference<HandlerSampleActivity> wr;

    public H(HandlerSampleActivity activity) {
        wr = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 判斷Activity是否被回收
        if(wr.get() != null) {
            // 若是Activity沒被回收的話就調用Activity的方法
            wr.get().doWork();
        }
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // activity銷燬時移除handler全部未被處理的callback和Message
    handler.removeCallbacksAndMessages(null);
}
複製代碼

由於Java的靜態內部類不會持有外部類的引用,因此咱們把Handler聲明爲靜態的內部類,若是須要使用Activity的引用時須要使用WeakReference對Activity進行處理,WeakReference是弱引用,當GC發生時被持有的對象會被回收。

另外在Activity的onDestroy()中調用handler.removeCallbacksAndMessages(null);參數爲null表示移除handler中全部未被處理的callbackMessage,這樣就不會出現Activity內存泄漏的狀況了。

優先使用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) {
        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.obtain()的源碼能夠看出Message中有一個全局的消息池——sPool。使用Message.obtain()方法會優先在消息池——sPool中線獲取Message,若是沒有可用消息纔會調用new Message()建立一個新消息。這樣作的優勢就是能夠避免沒必要要的內存分配。

上面的演示代碼能夠在Github中找到演示代碼Gihub地址

Android消息機制在面試中是常客,學習消息機制閱讀源碼是必須的,消息機制也就是Handler的運行機制。學習Handler運行機制主要涉及四個類。

Handler:發送或處理消息。

MessageQueue:消息隊列,用來保存消息。

Looper:從MessageQueue獲取消息,交給Handler處理。

ThreadLocal:負責切換線程。

ThreadLocal源碼

爲了能更好的理解後面的內容咱們須要先講一下ThreadLocalThreadLocal是一個泛型類,它保存一個泛型類型的數據。ThreadLocal使用場景並很少,平時用的很少。咱們先經過一段代碼看看ThreadLocal能幹什麼。

// 建立一個ThreadLocal對象用來保存一個String
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 建立一個普通的String變量
private String localString = "Main Thread";

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

    // 修改threadLocal的String
    threadLocal.set("Main Thread");
    Log.d(TAG, "threadLocal: " + threadLocal.get() + ", thread: " +  Thread.currentThread().getName());
    Log.d(TAG, "localString: " + localString + ", thread: " +  Thread.currentThread().getName());

    new Thread("Thread-1") {
        @Override
        public void run() {
            // 修改threadLocal的String
            threadLocal.set("Thread-1");
            localString = "Thread-1";
            Log.d(TAG, "threadLocal: " + threadLocal.get() + ", thread: " +  Thread.currentThread().getName());
            Log.d(TAG, "localString: " + localString + ", thread: " +  Thread.currentThread().getName());
        }
    }.start();

    new Thread("Thread-2") {
        @Override
        public void run() {
            Log.d(TAG, "threadLocal: " + threadLocal.get() + ", thread: " +  Thread.currentThread().getName());
            Log.d(TAG, "localString: " + localString + ", thread: " +  Thread.currentThread().getName());
        }
    }.start();
}
複製代碼

輸出日誌:

D/ThreadLocal: threadLocal: Main Thread, thread: main
D/ThreadLocal: localString: Main Thread, thread: main
D/ThreadLocal: threadLocal: Thread-1, thread: Thread-1
D/ThreadLocal: localString: Thread-1, thread: Thread-1
D/ThreadLocal: threadLocal: null, thread: Thread-2
D/ThreadLocal: localString: Thread-1, thread: Thread-2
複製代碼

代碼中建立一個ThreadLocal<String>對象,它能夠保存一個String類型的數據,又聲明一個普通的String變量,初始值爲Main Thread,其目的是爲了與ThreadLocal<String>造成對比。在主線程中使用threadLocal.set();threadLocal設置一個新值。而後打印兩個變量的值和當前的線程名字(threadLocal.get()能夠獲取到threadLocal.set();的值)。下面有新建了兩個線程,在Thread-1中分別對兩個變量進行賦值並打印其值和線程名字。在Thread-2中直接打印兩個變量的值和線程名字。

從輸出日誌能夠看出前兩次的打印結果兩個變量的值是相同的,結合代碼也很好理解。須要說明的是在Thread-2線程中打印的結果出現了不一樣。打印localString的值是Thread-1,這是由於localString最後一次賦值就是Thread-1,這個也很好理解,奇怪的是threadLocal最後打印的是null,而不是Thread-1。這就是ThreadLocal與普通變量的不一樣之處了。

ThreadLocal會爲每一個線程建立一個副本,它們互補干擾。因此纔會有上面的結果。咱們經過源碼來分析一下它是怎麼作到的。

咱們從ThreadLocalset()方法開始看。

// 泛型T就是ThreadLocal<T>指定的類型,value就是要保存的值,在多個線程中這個值相互獨立,不會被其餘線程所修改
public void set(T value) {
	// 獲取當前線程
    Thread t = Thread.currentThread();
    // 獲取當前線程中的threadLocals,threadLocals是
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 若是threadLocals不爲空則將ThreadLocal和value保存起來
        map.set(this, value);
    else
        // 若是threadLocals爲null則建立一個新的ThreadLocalMap來保存ThreadLocal和value
        createMap(t, value);
}
複製代碼

set方法中先獲取當前線程,而後在經過getMap方法獲取當前線程中的threadLocalsThread類中有一個成員變量threadLocals

public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}
複製代碼

若是threadLocals爲空則調用createMap(t, value);建立一個新的ThreadLocalMap保存ThreadLocalvalue

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
複製代碼

若是threadLocals不爲空則調用map.set(this, value);保存ThreadLocalvalue

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 計算ThreadLocal在table中的位置
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
複製代碼

這個方法的算法比較複雜,其重要的一點就是經過int i = key.threadLocalHashCode & (len-1);計算出ThreadLocal應該保存在table中的哪一個位置(在ThreadLocal中稱之爲槽位)。table的聲明以下:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private Entry[] table;
}
複製代碼

能夠看出ThreadLocalvalue以鍵值對的形式保存在table中指定位置,這個位置就是``int i = key.threadLocalHashCode & (len-1);計算出來的,而且他是惟一的,不一樣的ThreadLocal計算出來的值是不一樣的,不會出現衝突。若是threadLocals爲空的狀況在也會使用相同的算法來計算ThreadLocal位置的。

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    // 計算ThreadLocal在table中的位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 將ThreadLocal和value保存到table中
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
複製代碼

保存的邏輯梳理清楚了,在來看看看get()方法。

public T get() {
    // 獲取當前線程
    Thread t = Thread.currentThread();
    // 獲取當前線程的threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // threadLocals不爲空則查找ThreadLocal對應的Entry(鍵值對),key:ThreadLocal,value:以前保存的值。
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 若是沒找到返回一個初始值,默認是null
    return setInitialValue();
}
複製代碼

get()方法相對簡單,咱們主要關注map.getEntry(this);,看看如何取出以前保存的值。

private Entry getEntry(ThreadLocal<?> key) {
    // 計算ThreadLocal在table中的位置
    int i = key.threadLocalHashCode & (table.length - 1);
    // 取出Entry
    Entry e = table[i];
    if (e != null && e.get() == key)
        // 若是e不爲空而且是當前的ThreadLocal則返回Entry
        return e;
    else
        // 若是沒找到對應的ThreadLocal
        return getEntryAfterMiss(key, i, e);
}
複製代碼

能夠看到int i = key.threadLocalHashCode & (table.length - 1);又出現了,這也是ThreadLocal核心算法了。有興趣的能夠點這裏

上圖描述了咱們以前的演示代碼,每一個線程中都有一個ThreadLocalMap類型的threadLocals成員,其中包含了一個默認長度爲16的Entry類型數組table。當在線程中調用threadLocal.set(value)就會把value存到table指定位置中,這個位置就是經過int i = key.threadLocalHashCode & (table.length - 1);算出來的,其實還有一些尋找槽位的邏輯,這裏就不說明了。Thread-2線程沒有調用threadLocal.set(value)因此在調用threadLocal.get()時會調用setInitialValue()初始化一個默認值,也就是null。而在MainThreadThread-1中調用threadLocal.get()就會獲得以前set的值。

總結:

  • 多個線程調用set互影響,每一個線程中都會保存一份副本。
  • 每一個線程中只能get到當前線程的副本值。
  • 不調用set獲取的值是null,能夠經過重寫ThreadLocalinitialValue()方法改變默認值。

Message源碼

Message的源碼相對簡單,下面給出註釋。

public final class Message implements Parcelable {
    // 給消息編碼,以便接受者能區分消息是什麼類型或者用途。
    public int what;
    // 攜帶一個int類型數據
    public int arg1;
    // 攜帶一個int類型數據
    public int arg2;
    // 攜帶一個Object類型數據
    public Object obj;
    // 消息被髮送的時間
    /*package*/ long when;
	// 用來攜帶數據的Bundle,若是數據簡單有限考慮使用arg1/arg2和obj。
    /*package*/ Bundle data;
	// 發送消息的Handler引用。
    /*package*/ Handler target;
	// 用來處理消息的回調
    /*package*/ Runnable callback;
	// 消息隊列是以單鏈表形式存在的,next用於保存下一節點
    /*package*/ Message next;
    // 消息池
    private static Message sPool;
    // 消息池中當前有多少個消息
    private static int sPoolSize = 0;
    // 消息池最多保存50個Message
    private static final int MAX_POOL_SIZE = 50;

    // 試圖從消息池中取一個消息,若是消息池是空的則new一個新消息。
    // 新建Message是優先使用obtain系列方法,而不是使用new的方式。
    // 其餘obtain實現大同小異
    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
    public static Message obtain(Message orig) // 經過obtain()新建一個消息,把參數h賦值給Message的target。 public static Message obtain(Handler h) // 經過obtain()新建一個消息,把參數h賦值給Message的target。 // 把參數callback賦值給Message的callback。 // callback能夠用來處理消息,與handleMessage功能同樣 public static Message obtain(Handler h, Runnable callback) // 經過obtain()新建一個消息,把參數h賦值給Message的target。 // 把參數what賦值給Message的what。  public static Message obtain(Handler h, int what) // 經過obtain()新建一個消息,把參數h賦值給Message的target。 // 把參數what賦值給Message的what。 // 把參數obj賦值給Message的obj。 public static Message obtain(Handler h, int what, Object obj) public static Message obtain(Handler h, int what, int arg1, int arg2) public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) ... } 複製代碼

總結:

  • 消息池是以單鏈表的形式存在的,next成員保存鏈表中下一節點Message的引用。
  • target成員保存發送Message或處理Message的Handler引用。
  • 優先使用obtain系列方法獲取Message對象,能夠避免沒必要要的內存分配。

MessageQueue源碼

MessageQueue的主要用途就管理Message隊列。(源碼是經過單鏈表形式實現隊列操做)

// 將msg插入消息隊列中
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;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        // 將msg插入到隊列中
        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;
}
複製代碼

enqueueMessage是將Message插入到鏈表中的方法。

// 讀取消息
Message next() {
    ...
    // 無限循環讀取消息,若是沒有就會一直等待,若是有新消息當即返回
    for (;;) {
        ...
        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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            ...
        }

        ...
    }
}
複製代碼

next()方法用一個死循環讀取消息隊列中的消息,若是有新消息就會當即返回新消息,若是沒有新消息就會一直等待。當消息隊列爲空的狀態next方法還會阻塞當前線程。

// 中止消息循環
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);
    }
}
複製代碼

總結:

  • 消息隊列是用單鏈表實現的。
  • next()方法會阻塞線程,不用的時候調用quit()方法退出。
  • quit()有兩種方式。1. 處理完剩餘的消息在退出。2.直接退出。

Looper源碼

默認線程不具有消息循環能力,須要使用Looper.prepare()Looper.loop()開啓消息循環。Looper經過MessageQueue監控新消息,若是發現新消息則把消息交給Handler處理或者調用callback處理。

public final class Looper {
    // 使用ThreadLocal保證當前線程的Looper不受干擾
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    // 主線程Looper
    private static Looper sMainLooper;  // guarded by Looper.class
	// 消息隊列
    final MessageQueue mQueue;
    // 當前線程
    final Thread mThread;
    
    // 構造函數
    private Looper(boolean quitAllowed) {
        // 建立一個消息隊列
        mQueue = new MessageQueue(quitAllowed);
        // 獲取當前線程
        mThread = Thread.currentThread();
    }
    
    // 建立Looper
    public static void prepare() {
        prepare(true);
    }

    // 建立Looper
    private static void prepare(boolean quitAllowed) {
        // 若是重複調用prepare會報錯。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 使用ThreadLocal保存工做線程的Looper
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    // 建立主線程的Looper
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // sMainLooper保存主線程的Looper
            sMainLooper = myLooper();
        }
    }
}
複製代碼

由於線程默認沒有消息循環能力,因此須要使用Looper.prepareLooper.loop()開啓消息循環。主線程的消息循環是在ActivityThreadmain()方法中建立的。

public static void main(String[] args) {
    ...
	// 建立主線程Looper
    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"));
    }
	// 開啓主線程消息循環
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼

處理消息通常都須要一個Handler,主線程的消息都是由ActivityThread.H來處理的。

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;

    ....

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                // 應用退出時調用quit退出消息循環
                Looper.myLooper().quit();
                break;
                ...
    }    
}
複製代碼

H處理了系統重要組件的啓動和中止等過程。在應用退出時調用了Looper.myLooper().quit();退出消息循環,上面咱們講到MessageQueuenext()是一個死循環,若是不主動退出那麼就會一直運行,這會致使線程沒法退出。主線程的消息循環退出由系統處理了,咱們在子線程中使用Looper時必定要記得在不須要消息循環的時候主動退出消息循環。退出消息循環有兩種方式。

// 直接退出,移除消息隊列中全部消息
public void quit() {
    mQueue.quit(false);
}

// 將消息隊列中的消息處理完在退出。
public void quitSafely() {
    mQueue.quit(true);
}
複製代碼

能夠看到兩種退出方式實際上就是上面MessageQueue中講的兩種退出方式。關於Looper的源碼還差最終要的一個方法沒看,那就是loop()方法。

// 開啓當前線程的消息循環
public static void loop() {
    // 獲取當前線程的Looper
    final Looper me = myLooper();
    // 若是以前沒有調用Looper.prepare()建立Looper則會報錯。
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 當前線程的消息隊列
    final MessageQueue queue = me.mQueue;

    ...
    // 開啓消息循環,(一個死循環)
    for (;;) {
        // 調用queue.next(),看看有沒有新消息。再次提示queue.next()也是死循環,會阻塞線程。在不須要的時候要主動退出
        Message msg = queue.next(); // might block
        // 若是沒有新消息就結束消息循環了。
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        ...

        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        try {
            // 重點!重點!重點!注意看!這裏是handler能夠切換線程的關鍵。
          	// 咱們在Handler源碼一節詳細講解
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}
複製代碼

msg.target.dispatchMessage(msg);Handler能夠切換線程處理消息的重點,這句代碼必定要好好理解一下。

總結:

  • 調用Looper.prepare()建立Looper,調用Looper.loop()開啓消息循環。
  • 系統ActivityThreadmain()方法中開啓了主線程的消息循環,並在應用退出的時候使用Looper.myLooper().quit()退出了消息循環。
  • 屢次調用Looper.prepare()會拋出Only one Looper may be created per thread異常。
  • 調用Looper.loop()以前沒有調用Looper.prepare()會拋出No Looper; Looper.prepare() wasn't called on this thread.異常。
  • 在子線程中使用Looper要在不須要消息循環的時候調用Looper.quit()主動退出消息循環。不然主線程沒法釋放。

講了這麼多,最後的主角終於來了。

Handler源碼

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

// 指定消息循環的looper,默認是建立Handler線程的looper,也能夠本身指定,
// 例如在子線程建立Hanlder,這裏指定爲Looper.getMainLooper(),
// 那麼handlerMessage的處理消息的方法就會在主線程被執行。
// callback也是用來處理消息的,優先級高於handleMessage方法
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

...

@hide
public Handler(Callback callback, boolean async) {
    ...
	// 經過ThreadLocal獲取當前線程的Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        // 一般在子線程中沒有調用Looper.prepare()建立Looper會報錯
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    // 消息隊列
    mQueue = mLooper.mQueue;
    // 用來處理Message的回到,優先級高於handleMessage方法
    mCallback = callback;
    // 是不是異步消息,默認是同步消息。結合MessageQueue的postSyncBarrier方法能夠提升Message的優先級,能夠優先獲得處理。
    mAsynchronous = async;
}
複製代碼

先看一下Looper.myLooper()

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
複製代碼

myLooper()就是經過ThreadLocal獲取當前線程的Looper對象。若是是在主線程建立Handler它就是sMainLooper,若果是在子線程它就是子線程中用Looper.prepare()建立的Looper。因此這裏的mLooperHandler是在同一個線程中的。

  • 建立Handler默認使用當前線程的Looper。能夠經過構造函數指定Looper
  • 在子線程中建立Handler以前須要使用Looper.prepare()建立Looper,不然報錯。
  • 能夠指定一個Callback用來處理Message
  • 有關於async參數系統並不推薦咱們更改,有關方法已經被標記@hide

Handler建立完了,繼續看看Handler是如何發送消息的。

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

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}
複製代碼

上面的send方法最終都是經過sendMessageAtTime處理的,除了send系列方法,Handler還有一系列的post方法。

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

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

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

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

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

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

post系列方法則是經過getPostMessage方法構造一個攜帶Runnable對象Message,最終也是經過enqueueMessage進行統一處理了。

ActivityrunOnUiThread()方法就是用的post

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        // 若是不是UI線程則將Runnable添加到主線程的消息隊列等待執行。
        mHandler.post(action);
    } else {
        // 若是是UI線程則直接執行run方法
        action.run();
    }
}
複製代碼

全部的發送消息的方法最終都由enqueueMessage方法處理了。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 給Message中的target賦值,this就是用來發送和處理Message的handler對象。
    msg.target = this;
    // 是不是異步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 經過MessageQueue將消息插入消息隊列中。
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼

enqueueMessage首先給msg.target賦值,this就是用來發送和處理Message的handler對象。最終調用MessageQueueenqueueMessage方法將消息插入消息隊列中。消息被插入到消息隊列中以後就會被MessageQueuenext方法發現,以後交給LooperLooper經過msg.target.dispatchMessage(msg);將消息交給Handler處理。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 若是Message的callback不爲空則執行callback,這裏的callback實際是一個Runnable
        handleCallback(msg);
    } else {
        // 若是msg沒設置callback則判斷handler的callback,這裏的callback類型是Callback。
        if (mCallback != null) {
            // 在建立Handler的時候能夠指定一個Callback參數。
            // 若果有callback則把消息交給callback處理,若是callback返回true則處理完成,
            // 不然在將消息交給handler的handleMessage方法處理
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 這就是咱們重寫的handleMessage方法,用來處理消息。
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    // 執行Runnable的run方法
    message.callback.run();
}

// 被咱們重寫的方法,用來處理消息
public void handleMessage(Message msg) {
}

// new Handler時候能夠指定一個Callback,用來處理消息,優先級比handleMessage高。
public interface Callback {
    public boolean handleMessage(Message msg);
}
複製代碼

到這裏有關消息機制的源碼就分析的差很少了。咱們把上面所講的內從串聯起來造成一張圖。

由於HandlerLooperMessageQueue都是在主線程建立的,因此handler.sendMessage(message)所發送的消息也被插入到了主線程的消息隊列中了,而後在交給主線程的msg.target.dispatchMessage(msg)分發處理。這樣就完成了一條消息的處理。

Android的消息機制是很重要的,不管是平常工做,仍是源碼學習,或使面試都是離不開它的。因此真正掌握消息機制是頗有必要的。

至此Android的消息機制就分析完了,文中有哪些不足之處歡迎到個人Github指正。

郵箱:eonliu1024@gmail.com

Github: github.com/Eon-Liu

相關文章
相關標籤/搜索