Android開發之漫漫長途 Ⅶ——Android消息機制(Looper Handler MessageQueue Message)
前言
上一篇我們介紹了LeakCanary工具用來分析內存泄漏以及談了下幾種常見內存泄漏的表現和解決方法。本篇內容我們來分析Android的消息機制。我們為什么要介紹Android的消息機制呢,因為Android系統本質上來說就是一個消息驅動的系統。我們在開發中什么時候會用到Handler呢,工作年限較長的開發工程師應該對這個Handler很熟悉了,因為在早期的開發中,無論是網絡請求刷新UI還是子線程耗時任務的通知的應用場景都能看到Handler的身影。現在Handler在我們的日常開發中少了一些,因為我們有了RxJava、Retrofit等對Handler進行了很精美的封裝。但是理解Android的消息機制對于理解Android系統的運作包括那些開源框架的原理都有很大幫助。
關于Android的消息機制網上也有好多文章,我本人也看了好多。但是不僅沒有讓我更清晰明了,反而讓我陷入更深的迷惑。本篇的目的在于以一種相對更容易理解的方式來解釋。
我們先來模擬一個場景,在Activity中執行了耗時操作,耗時操作完成之后顯示一個Toast。這種應用場景還是比較常見的。我們來模擬代碼。
public class MessageActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模擬耗時操作
Thread.sleep(3* 60 * 1000);
//耗時操作完成之后顯示一個通知
Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
我們來運行上面的代碼。呦呵,崩潰了,我們查看日志得到以下信息。
關于上面的崩潰我們稍后分析。
ActivityThread
既然討論Android 消息機制,如果我們所有的操作都能在一個線程中完成貌似就不需要這個消息處理機制了,,但這又是不現實的,正是因為我們不能在一個線程中把所有的工作(網絡請求、耗時操作、更新UI)在一個線程中完成,我們才有了多線程,多線程的互相協作才造就了我們這個Android欣欣向榮的世界。由此我們不得不說到我們Android App中的主線程(UI)線程,關于這個線程的叫法有很多。讀者只需要知道不能在這個線程之外的線程直接對UI進行操作就行了。Android 4.0 以上甚至不能在主線程中(UI線程)中進行網絡操作。否則的話會報android.os.NetworkOnMainThreadException,這個錯誤大家應該都見過把。那我們就從這個主線程(UI線程說起)。
public static void main(String[] args) {
......
//1 創建Looper 和 MessageQueue,本來該線程也是一個普通的線程,但是創建了Looper以及結合后文的Looper.loop()方法,使這個線程成為了Looper線程(讀者可以簡單的理解為擁有Looper的線程,而這個Looper就是Android消息處理機制的一部分)。
Looper.prepareMainLooper();
//2 建立與AMS的通信
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
//3 無限循環
Looper.loop();
//可以看出來主線程也是在無限的循環的,異常退出循環的時候會報錯.
throw new RuntimeException("Main thread loop unexpectedly exited");
}
1 創建Looper 和 MessageQueue
public final class Looper {
......
public static void prepare() {
prepare(true);
}
//prepare 函數
private static void prepare(boolean quitAllowed) {
//判斷sThreadLocal.get()是否為空,如果不為空說明已經為該線程設置了Looper,不能重復設置。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//如果sThreadLocal.get()為空,說明還沒有為該線程設置Looper,那么創建Looper并設置
sThreadLocal.set(new Looper(quitAllowed));
}
//ActivityThread 調用Looper.prepareMainLooper();該函數調用prepare(false);
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
......
}
在這里呢有個靜態變量sThreadLocal,它的定義如下
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
那么我們就得來講解ThreadLocal這個類:
線程本地存儲區(Thread Local Storage,簡稱為TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域。這里線程自己的本地存儲區域存放是線程自己的Looper。簡單的來說就是通過ThreadLocal來進行Looper的統一存儲和讀取。那么接著來看被ThreadLocal存儲的對象Looper的構造函數。
//Looper的構造函數
private Looper(boolean quitAllowed) {
//這里創建了MessageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
這里創建了MessageQueue為后續的步驟做準備,MessageQueue可以簡單理解為一個“隊列”(其底層實際上是一個單向鏈表),之所以是打上引號的“隊列”,是因為其并不是嚴格意義上的隊列,而是一個單項鏈表,使用者可以根據節點的優先級等等插入該鏈表。鏈表上的節點就是Message。第①步 整個的結構圖如下所示
2 建立與AMS的通信
關于這一部分內容必須得對Android Binder知識有相關了解才能更好的理解。我們下一篇就會講解Android Binder,到時候我們在回來這里。
3 無限循環
在上面的工作中我們已經準備好Looper和MessageQueue,下面就有了兩個問題,① Message從何而來,② Message如何處理。
Message
我們在討論Message的來源以及如何處理之前,先來看一下Message類
public class Message{
//消息碼
public int what;
//handler
Handler target;
//下一級節點
Message next;
//消息發送的時間
long when;
}
上面的代碼也從側面證明了我們的MessageQueue是一個由Message組成的單向鏈表
我們先來看Message如何處理,至于為什么,當然是保證因為我們的思路不被打斷,我們先分析ActivityThread的最后Looper.loop()函數做了什么。
② Message如何處理
我們來到了ActivityThread的最后一步Looper.loop()
ActivityThread.java
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.");
}
//得到MessageQueue
final MessageQueue queue = me.mQueue;
......
for (;;) {//無限循環
Message msg = queue.next(); // 取下一個Message 可能阻塞在這里
if (msg == null) {
//如果隊列為空直接return直接結束了該方法,即循環結束
return;
}
......
try {
//分發message到指定的target handler
msg.target.dispatchMessage(msg);
......
} finally {
}
......
}
}
主線程由此進入無限循環等待消息,有人看到這里就由疑問了,執行到for循環時,不就“卡死”在這個無限循環內了嗎?其他的操作無法得到CPU怎么執行呢?關鍵點就在于queue.next()方法。
為了更好的理解這個方法我們先來講一下關于線程阻塞與喚醒的知識
線程阻塞
什么是阻塞呢?比如某個時候你在等快遞,但是你不知道快遞什么時候過來,而且你沒有別的事可以干(或者說接下來的事要等快遞來了才能做);那么你可以去睡覺了,因為你知道快遞把貨送來時一定會給你打個電話(假定一定能叫醒你)。結合我們上面的代碼。我們的代碼運行Message msg = queue.next();這一句時,主線程可能一直阻塞在這里等待消息的到來(它去睡覺去了,也就是說我們的主線程,居然是大部分時間都在睡覺,心真大啊)。
注:線程阻塞跟線程忙循環輪詢是有本質區別的,不要聽到線程阻塞就以為是CPU一直在無限循環輪詢狀態啊。線程阻塞是不占用CPU資源的,但是線程忙循環輪詢就不一樣了,將幾乎占滿CPU資源。什么是CPU資源,簡單的來說CPU資源就是分配給程序的執行時間。
線程喚醒
要想把主線程活動起來一般有兩種方式:一種是系統喚醒主線程,并且將點擊事件傳遞給主線程;第二種是其他線程使用Handler向MessageQueue中存放了一條消息,導致loop被喚醒繼續執行。在下面的Message從何而來中我們這里使用了hander向MessageQueue中存放了一條消息,導致loop被喚醒繼續執行。
① Message從何而來
public class MessageActivity extends AppCompatActivity {
private Handler mHandler= new Handler(){
//處理消息
@Override
public void handleMessage(Message msg) {
handleMsg(msg);
}
};
private void handleMsg(Message msg) {
switch (msg.what){
case 0:
Toast.makeText(this,"成功",Toast.LENGTH_SHORT).show();
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模擬耗時操作
Thread.sleep(3*1000);
//發送消息
mHandler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
我們經常使用上面的代碼來做耗時操作,那么這里這里我們的豬腳就出場了,mHandler是Handler的對象。我們來看一下Handler類
public class Handler {
//handler類有個Looper
final Looper mLooper;
//handler類有個MessageQueue
final MessageQueue mQueue;
//handler類有個Callback
final Callback mCallback;
public Handler() {//我們使用的是這一個
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback, boolean async) {
//這里獲取主線程的Looper,Handler的mLooper指向ThreadLocal內的Looper對象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//這里獲取主線程的Looper的MessageQueue,Handler的mQueue指向ThreadLocal內Looper對象內的MessageQueue對象
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
}
創建Handler 之后就調用 mHandler.sendEmptyMessage(0);發送消息(Handler的發送消息的方式有好多種,但這不是我們的重點),最終調用到Handler enqueueMessage 方法
Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
設置msg.target 為當前Handler對象
msg.target = this;
......
//調用MessageQueue的enqueueMessage()方法
return queue.enqueueMessage(msg, uptimeMillis);
}
我們再來看一下MessageQueue的enqueueMessage()
MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
msg.when = when;
Message p = mMessages;
//檢測當前頭指針是否為空(隊列為空)或者沒有設置when 或者設置的when比頭指針的when要前
if (p == null || when == 0 || when < p.when) {
//插入隊列頭部,并且喚醒線程處理msg
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 幾種情況要喚醒線程處理消息:1)隊列是堵塞的 2)barrier,頭部結點無target 3)當前msg是堵塞的
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; // 將當前msg插入第一個比其when值大的結點前。
prev.next = msg;
}
//調用Native方法進行底層操作,在這里把那個沉睡的主線程喚醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
我們的Handler在發送消息的時候把自身設置給了msg.target,發送消息并喚醒Looper,Looper被喚醒后便使用queue.next()取出Message,并根據msg.target進行派發。Handler整體過程如下圖
我們再稍微看下Handler的dispatchMessage方法
Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//判斷有沒有為Message設置callback(這里的callback是個Runnable接口,我們在為Message設置callback的時候需要自己實現run方法),如果設置了,那么調用Runnable實例的run方法
handleCallback(msg);
} else {
if (mCallback != null) {//判斷Handler的mCallback是否為空(這里的Handler是個Callback接口,我們在為Handler設置mCallback的時候需要自己實現handleMessage方法),如果設置了,那么調用Callback實例的handleMessage方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//調用handleMessage方法
handleMessage(msg);
}
}
我們在創建Handler使用的是無法構造函數,并重寫了handleMessage方法,所以我們的重寫的handleMessage得到調用,彈出了Toast
本篇總結
本篇比較詳細的介紹了Android的消息機制,不過有一部分內容需要其他的知識作為基礎才能更好的理解。不過這不影響我們分析Android的消息機制的整個流程。我們在這里再梳理一下。
1. 主線程準備Looper和MessageQueue
2. 創建一個線程(因為下面我們進入死循環了,所以在這之前創建一個線程用來處理,這是個Binder線程)
3. 主線程進入無限循環等待并處理消息。(這個消息可能是系統本身的消息,也有可能是我們自己的消息。在本例中分析的是我們自己創建的Handler發送的消息。)
我們再上個整圖
這里呢我們呢是使用Activity的創建作為分析,因為這是Activity的起點。在注釋第2步中的代碼sendMessage(H.LAUNCH_ACTIVITY, r);與我們例子中mHandler.sendEmptyMessage(0);并沒有什么大的不同。
現在也是揭曉我們文章開頭的那個崩潰的秘密的時候了,相信讀者也有答案了。沒錯,是因為我們在非UI線程中更新了UI,導致了異常。原因是我們在子線程沒有Looper啊。你可以做出如下更改就不會有異常了。
public class MessageActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模擬耗時操作
Thread.sleep(3* 60 * 1000);
//在子線程中更新UI之前,先準備一個Looper,與主線程相同
if (Looper.myLooper() != null){
Looper.prepare();
}
//耗時操作完成之后顯示一個通知
Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show();
//無限循環
Looper.loop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
來自:http://www.cnblogs.com/wangle12138/p/8032687.html