文章出處:[Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解](https://blog.csdn.net/guolin_blog/article/details/9991569)
我們都知道,**Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程序就有可能會崩潰**。相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也是早已爛熟于心,即**創建一個Message對象,然后借助Handler發送出去,之后在Handler的handleMessage()方法中獲得剛才發送的Message對象,然后在這里進行UI操作就不會再出現崩潰了**。這種處理方式被稱為**異步消息處理線程**,雖然我相信大家都會用,可是你知道它背后的原理是什么樣的嗎?
今天我們就來一起深入探究一下Handler和Message背后的秘密。
首先來看一下如何創建Handler對象。你可能會覺得挺納悶的,創建Handler有什么好看的呢,直接new一下不就行了?確實,不過即使只是簡單new一下,還是有不少地方需要注意的,我們**嘗試在程序中創建兩個Handler對象,一個在主線程中創建,一個在子線程中創建**,代碼如下所示:
```
public class MainActivity extends Activity {
private Handler handler1;
private Handler handler2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler1 = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler2 = new Handler();
}
}).start();
}
}
```
如果現在運行一下程序,你會發現,在子線程中創建的Handler是會導致程序崩潰的,提示的錯誤信息為 `Can't create handler inside thread that has not called Looper.prepare() `。說是不能在沒有調用Looper.prepare() 的線程中創建Handler,那我們**嘗試在子線程中先調用一下Looper.prepare()呢**,代碼如下所示:
```
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler2 = new Handler();
}
}).start();
```
果然這樣就不會崩潰了,不過只滿足于此顯然是不夠的,我們來看下[Handler](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/os/Handler.java)的源碼,搞清楚為什么不調用Looper.prepare()就不行呢。Handler的無參構造函數如下所示:
```
public 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 = null;
}
```
可以看到,在第13行調用了`Looper.myLooper()`方法獲取了一個[Looper](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/os/Looper.java)對象,如果Looper對象為空,則會拋出一個運行時異常,提示的錯誤正是 `Can't create handler inside thread that has not called Looper.prepare()`!那什么時候Looper對象才可能為空呢?這就要看看`Looper.myLooper()`中的代碼了,如下所示:
```
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
```
這個方法非常簡單,就是從sThreadLocal對象中取出Looper。**ThreadLocal**: 線程本地存儲區(Thread Local Storage,簡稱為TLS),**每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域**
如果sThreadLocal中有[Looper](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/os/Looper.java)存在就返回Looper,如果沒有Looper存在自然就返回空了。
因此你可以想象得到是在哪里給sThreadLocal設置Looper了吧,當然是Looper.prepare()方法!我們來看下它的源碼:
```
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));
}
```
可以看到,首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則創建一個新的Looper設置進去。**這樣也就完全解釋了為什么我們要先調用Looper.prepare()方法,才能創建Handler對象。同時也可以看出每個線程中最多只會有一個Looper對象**。咦?不對呀!**主線程中的Handler也沒有調用Looper.prepare()方法,為什么就沒有崩潰呢**?細心的朋友我相信都已經發現了這一點,這是**由于在程序啟動的時候,系統已經幫我們自動調用了Looper.prepare()方法**。查看[ActivityThread](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/app/ActivityThread.java)中的main()方法,代碼如下所示:
```
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// 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"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
```
可以看到,在第18行調用了Looper.prepareMainLooper()方法,而這個方法又會再去調用Looper.prepare()方法,代碼如下所示:
```
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
```
因此**我們應用程序的主線程中會始終存在一個Looper對象,從而不需要再手動去調用Looper.prepare()方法了**。這樣基本就將Handler的創建過程完全搞明白了,
總結一下就是**在主線程中可以直接創建Handler對象,而在子線程中需要先調用Looper.prepare()才能創建Handler對象**。
看完了如何創建Handler之后,接下來我們看一下如何發送消息,這個流程相信大家也已經非常熟悉了,new出一個Message對象,然后可以使用setData()方法或arg參數等方式為消息攜帶一些數據,再借助Handler將消息發送出去就可以了,示例代碼如下:
```
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.arg1 = 1;
Bundle bundle = new Bundle();
bundle.putString("data", "data");
message.setData(bundle);
handler.sendMessage(message);
}
}).start();
```
可是這里**Handler到底是把Message發送到哪里去了呢?為什么之后又可以在Handler的handleMessage()方法中重新得到這條Message呢**?看來又需要通過閱讀源碼才能解除我們心中的疑惑了,Handler中提供了很多個發送消息的方法,其中**除了sendMessageAtFrontOfQueue()方法之外,其它的發送消息方法最終都會輾轉調用到sendMessageAtTime()方法**中,這個方法的源碼如下所示:
```
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);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
```
sendMessageAtTime()方法接收兩個參數,其中msg參數就是我們發送的Message對象,而uptimeMillis參數則表示發送消息的時間,它的值等于自系統開機到當前時間的毫秒數再加上延遲時間,如果你調用的不是sendMessageDelayed()方法,延遲時間就為0,然后將這兩個參數都傳遞到[MessageQueue](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/os/MessageQueue.java)的enqueueMessage()方法中。
這個**MessageQueue又是什么東西呢**?其實從名字上就可以看出了,**它是一個消息隊列,用于將所有收到的消息以隊列的形式進行排列,并提供入隊和出隊的方法**。
```
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
```
**MessageQueue這個類是在Looper的構造函數中創建的**,因此**一個Looper也就對應了一個MessageQueue**。那么enqueueMessage()方法毫無疑問就是入隊的方法了,我們來看下這個方法的源碼:
```
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
```
每個消息用Message表示,Message主要包含以下內容:

首先你要知道,MessageQueue并沒有使用一個集合把所有的消息都保存起來,它只使用了一個mMessages對象表示當前待處理的消息。
然后觀察上面的代碼,從`msg.when = when;`開始,我們就可以看出,**所謂的入隊其實就是將所有的消息按時間來進行排序,這個時間當然就是我們剛才介紹的uptimeMillis參數**。
具體的操作方法就**根據時間的順序調用`msg.next`,從而為每一個消息指定它的下一個消息是什么。當然如果你是通過sendMessageAtFrontOfQueue()方法來發送消息的,它也會調用enqueueMessage()來讓消息入隊,只不過時間為0,這時會把mMessages賦值為新入隊的這條消息,然后將這條消息的next指定為剛才的mMessages,這樣也就完成了添加消息到隊列頭部的操作**。
現在入隊操作我們就已經看明白了,那出隊操作是在哪里進行的呢?這個就需要看一看Looper.loop()方法的源碼了,如下所示:
```
public static void loop() {
final Looper me = myLooper(); //獲取TLS存儲的Looper對象 【見2.4】
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //獲取Looper對象中的消息隊列
Binder.clearCallingIdentity();
//確保在權限檢查時基于本地進程,而不是基于最初調用進程。
final long ident = Binder.clearCallingIdentity();
for (;;) { //進入loop的主循環方法
Message msg = queue.next(); //可能會阻塞 【見4.2】
if (msg == null) { //沒有消息,則退出循環
return;
}
Printer logging = me.mLogging; //默認為null,可通過setMessageLogging()方法來指定輸出,用于debug功能
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg); //用于分發Message 【見3.2】
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity(); //確保分發過程中identity不會損壞
if (ident != newIdent) {
//打印identity改變的log,在分發消息過程中是不希望身份被改變的。
}
msg.recycleUnchecked(); //將Message放入消息池 【見5.2】
}
}
```
可以看到,這個方法從`for (;;)`開始,進入了一個死循環,然后不斷地調用的MessageQueue的next()方法,我想你已經猜到了,**這個next()方法就是消息隊列的出隊方法**。不過由于這個方法的代碼稍微有點長,我就不貼出來了,它的簡單邏輯就是**如果當前MessageQueue中存在mMessages(即待處理消息),就將這個消息出隊,然后讓下一條消息成為mMessages,否則就進入一個阻塞狀態,一直等到有新的消息入隊**。
繼續看loop()方法的`msg.target.dispatchMessage(msg); `,每當有一個消息出隊,就將它傳遞到msg.target的dispatchMessage()方法中,那這里**msg.target又是什么呢?其實就是Handler啦**,你觀察一下上面**enqueueMessage**()方法的第2行`msg.target = this;`,這里的this就是Handler,就可以看出來了。
接下來當然就要看一看Handler中dispatchMessage()方法的源碼了,如下所示:
```
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
```
在第5行進行判斷,**如果mCallback不為空,則調用mCallback的handleMessage()方法,否則直接調用Handler的handleMessage()方法,并將消息對象作為參數傳遞過去**。
這樣我相信大家就都明白了**為什么handleMessage()方法中可以獲取到之前發送的消息了吧**!因此,一個最標準的異步消息處理線程的寫法應該是這樣:
```
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
```
當然,這段代碼是從Android官方文檔上復制的,不過大家現在再來看這段代碼,是不是理解的更加深刻了?
那么我們還是要來繼續分析一下,**為什么使用異步消息處理的方式就可以對UI進行操作了呢**?這是**由于Handler總是依附于創建時所在的線程,比如我們的Handler是在主線程中創建的,而在子線程中又無法直接對UI進行操作,于是我們就通過一系列的發送消息、入隊、出隊等環節,最后調用到了Handler的handleMessage()方法中,這時的handleMessage()方法已經是在主線程中運行的,因而我們當然可以在這里進行UI操作了**。
* [ ] **Handler總是依附于創建時所在的線程,意思就是:你在子線程A里創建了一個Handler,不管在任何其它地方使用這個Handler去發送消息時,最后肯定會在子線程A里去處理這個消息,也就是在handleMessage()方法里去執行具體的邏輯操作并不會阻塞UI線程,也就不需要另開一個線程了**。
整個異步消息處理流程的示意圖如下圖所示:

另外除了發送消息之外,我們還有以下幾種方法可以在子線程中進行UI操作:
1. Handler的post()方法
2. View的post()方法
3. Activity的runOnUiThread()方法
我們先來看下Handler中的post()方法,代碼如下所示:
```
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
```
原來這里還是調用了sendMessageDelayed()方法去發送一條消息啊,并且還使用了getPostMessage()方法將Runnable對象轉換成了一條消息,我們來看下這個方法的源碼:
```
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
```
在這個方法中**將消息的callback字段的值指定為傳入的Runnable對象**。咦?這個callback字段看起來有些眼熟啊,喔!在Handler的dispatchMessage()方法中原來有做一個檢查,如果Message的callback等于null才會去調用handleMessage()方法,否則就調用handleCallback()方法。那我們快來看下handleCallback()方法中的代碼吧:
```
private static void handleCallback(Message message) {
message.callback.run();
}
```
也太簡單了!**竟然就是直接調用了一開始傳入的Runnable對象的run()方法**。
因此在**子線程中通過Handler的post()方法進行UI操作就可以這么寫**:
```
public class MainActivity extends Activity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
// 在這里進行UI操作
}
});
}
}).start();
}
}
```
雖然寫法上相差很多,但是原理是完全一樣的,我們**在Runnable對象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI**。
>[success]這里注解一下:Handler的post()源碼中,我們可以看到,他里面調用了sendMessageDelayed,進而跳轉到sendMessageAtTime,最終通過enqueueMessage插入消息隊列,而Handler總是依附于創建時所在的線程,很明顯這個handler是在主線程中創建,那么最后的消息隊列也是在主線程,所以這里可以進行UI更新操作。Handler的post方法將一個Runnable投遞到Handler內部的Looper中去處理,其實post方法最終也是通過send方法來完成的。然后Looper發現有新消息到來時,就會處理這個消息,最終消息中的Runnable或者Handler的handleMessage方法就會被調用。注意Looper是運行在創建Handler所在的線程中的,這樣一來Handler中的業務邏輯就被切換到創建Handler所在的線程中去執行了而且,一個線程對應一個Looper和一個MessageQueue,Handler可以有多個,里面的looper和message是同一份。
然后再來看一下View中的post()方法,代碼如下所示:
```
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
```
原來就是調用了Handler中的post()方法——`attachInfo.mHandler.post(action)`,我相信已經沒有什么必要再做解釋了。
最后再來看一下**Activity中的runOnUiThread**()方法,代碼如下所示:
```
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
```
**如果當前的線程不等于UI線程(主線程),就去調用Handler的post()方法,否則就直接調用Runnable對象的run()方法**。還有什么會比這更清晰明了的嗎?通過以上所有源碼的分析,我們已經發現了,**不管是使用哪種方法在子線程中更新UI,其實背后的原理都是相同的,必須都要借助異步消息處理的機制來實現**,而我們又已經將這個機制的流程完全搞明白了,真是一件一本萬利的事情啊。
* [ ] **延伸**:
Loopper存在ThreadLocal里面,handler依附于創建它的進程,那么如果在子線程里面創建handler最終的handler.dispatchMessage()也是在子線程中執行的,那么也不能更新主界面UI啊。一般情況下,我們不是依賴于handler.sendMsg,然后這個handler在主線程創建,來更新UI界面么;那么,請問在子線程創建handler有什么具體作用呢?或者說為啥要在在子線程創建一個handler呢?
系統之所以提供Handler,主要原因就是為了解決在子線程中無法訪問UI的矛盾,但是Handler并不是專門用于更新UI的,它只是常被我們用來更新UI而已,至于在子線程創建Handler,一般是用于解決復雜邏輯下的對象傳遞。
- 前言
- Android系統的體系結構
- Dalvik VM 和 JVM 的比較
- Android 打包應用程序并安裝的過程
- Android ADB工具
- Android應用開發
- Android UI相關知識總結
- Android 中window 、view、 Activity的關系
- Android應用界面
- Android中的drawable和bitmap
- AndroidUI組件adapterView及其子類和Adapter的關系
- Android四大組件
- Android 數據存儲
- SharedPreference
- Android應用的資源
- 數組資源
- 使用Drawable資源
- Material Design
- Android 進程和線程
- 進程
- 線程
- Android Application類的介紹
- 意圖(Intent)
- Intent 和 Intent 過濾器(Google官網介紹)
- Android中關于任務棧的總結
- 任務和返回棧(官網譯文)
- 總結
- Android應用安全現狀與解決方案
- Android 安全開發
- HTTPS
- 安卓 代碼混淆與打包
- 動態注入技術(hook技術)
- 一、什么是hook技術
- 二、常用的Hook 工具
- Xposed源碼剖析——概述
- Xposed源碼剖析——app_process作用詳解
- Xposed源碼剖析——Xposed初始化
- Xposed源碼剖析——hook具體實現
- 無需Root也能Hook?——Depoxsed框架演示
- 三、HookAndroid應用
- 四、Hook原生應用程序
- 五、Hook 檢測/修復
- Android 應用的逆向與加固保護技術
- OpenCV在Android中的開發
- Android高級開發進階
- 高級UI
- UI繪制流程及原理
- Android新布局ConstraintLayout約束布局
- 關鍵幀動畫
- 幀動畫共享元素變換
- Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解
- Android中為什么主線程不會因為Looper.loop()里的死循環卡死?
- 為什么 Android 要采用 Binder 作為 IPC 機制?
- JVM 中一個線程的 Java 棧和寄存器中分別放的是什么?
- Android源碼的Binder權限是如何控制?
- 如何詳解 Activity 的生命周期?
- 為什么Android的Handler采用管道而不使用Binder?
- ThreadLocal,你真的懂了嗎?
- Android屏幕刷新機制