Handler非同步訊息處理機制的原始碼分析
1. Handler非同步訊息處理機制
- 主執行緒不能進行耗時操作 (會發生ANR異常)
子執行緒不能更新UI的操作 (因為會報異常——>只有產生該控制元件的執行緒才能操作該控制元件。)
那麼 , 要在子執行緒請求資料,然後將資料傳送到主執行緒中更新UI , 此時需要使用Handler機制.
2. Handler的核心組成部分
Handler : 用於傳送訊息和處理訊息
Message : 用於攜帶資料和通知
MessageQueue : 以單鏈表的形式用於儲存訊息
Looper : 死迴圈,如果訊息佇列裡有訊息就將其取出然後交給對應的Handler處理,如果沒有就處於等待狀態
3. Handler的非同步訊息處理流程圖
首先需要在主執行緒中建立一個Handler物件 , 並重寫handleMessage()方法 , 然後當子執行緒中需要進行UI操作時 , 就建立一個Message物件 , 並通過Handler將這條資訊傳送出去 , 之後這條資訊會被新增到MessageQueue的佇列中等待被處理 , 而Looper則會一直嘗試從MessageQueue中取出待處理訊息 , 最後分發會Handler的handlerMessage()方法中.由於Handler是在主執行緒中建立的 , 所以此時handlerMessage()方法中的程式碼也會在主執行緒中執行 , 進行UI操作.
非同步訊息處理執行緒啟動後會進入一個無限的迴圈體之中,每迴圈一次,從其內部的訊息佇列中取出一個訊息,然後回撥相應的訊息處理函式,執行完成一個訊息後則繼續迴圈。若訊息佇列為空,執行緒則會阻塞等待。
4. Handler的簡單使用方法
- 在主執行緒中建立一個Handler物件 , 重寫裡面的方法
// 建立一個Handler物件
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 處理訊息
}
};
- 在子執行緒中通過mHandler傳送資訊
// 建立一個Message物件
Message msg = Message.obtain();
// 將訊息存入Message物件中
msg.obj = null;
// 給訊息新增標記 , 例如 SUCCESS 和 FAILED
msg.what = 0;
// 通過mHandler傳送訊息到主執行緒
mHandler.sendMessage(msg);
- 在主執行緒中進行操作
handle.post(new Runnable() {
@Override
public void run() {
}
});
5. Handler的原始碼分析
(1) 首先 , 我們來看看Message物件的建立 , 一共有3種方式
Message msg = new Message();
Message msg = Message.obtain();
// 傳送訊息
handler.sendMessage(msg);
// handler已經繫結
Message msg = handler.obtainMessage();
// 傳送訊息
msg.sendToTarget();
handler.obtainMessage()方法裡底層實質上也是呼叫了Message的obtain(handler)方法 , 然後通過sendToTarget()傳送出去
public final Message obtainMessage()
{
return Message.obtain(this);
}
Message繼承了Parcelable介面 , 可實現序列化 , 那麼 , 接下來 , 我們看看Message物件裡的obtain()方法 , 裡面到底做了什麼 :
public final class Message implements Parcelable
/*package*/ Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
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();
}
從中可以看到一個同步塊 , 通過一個new的object物件進行加鎖 , 然後 , 在同步塊裡 , 如果sPool不為null , 那麼將sPool賦值給了m , 最後返回出來 , 如果sPool為null , 那麼就重新new一個Message物件.
這時 , 大家一定奇怪了 , 第11行和第12行的這兩行程式碼又代表著什麼 , 其實 , 這裡訊息池是一個單鏈表結構 , 這兩行程式碼意味著從訊息池中取出Message物件後 , 對sPool的位置進行重置.
然後 , 我們再來看Message的obtain(Handler h)方法 :
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
其中 , target就是對Handler進行標記 , 比如一個應用中建立了多個Handler物件來發送訊息 , 那麼就可以用target來進行區分.
好 , 我們接下來再看一下Message裡的recycle()方法 , 對訊息進行回收 , 裡面的屬性都設定為預設值 :
public void recycle() {
...
recycleUnchecked();
}
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++;
}
}
}
(2) 我們再走進Handler物件中 , 切入主入口 , Handler的例項化
public Handler() {
this(null, false);
}
實質上是呼叫了它的過載方法 , 傳入兩個引數 , null 和 false , 如下 :
public Handler(Callback callback, boolean async) {
// 此處省略一萬行程式碼
...
// 輪詢器,輪詢取訊息
mLooper = Looper.myLooper();
// 訊息佇列,存放handler發的訊息
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
關鍵的部分來了 , 第6行和第9行程式碼 , 獲取了輪詢器的物件mLooper , 以及訊息佇列mQueue , 此處表示handler和mLooper共享同一個訊息佇列 . 那我們首先來看mLooper的初始化 , Looper.myLooper()方法 :
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
其中 , sThreadLocal是代表本地執行緒變數 , 每個執行緒都可以去呼叫它 , 它也知道是哪個執行緒在存放資料 , 那麼這裡的get()方法 , 就是去獲取資料 , 這時 , 你一定奇怪它是什麼時候存的呢?這裡要提到一個ActivityThread類 , 代表主執行緒 , 在應用程式執行之前 , ActivityThread類就已經初始化了 , 我們來看裡面的核心程式碼 :
//主執行緒
public static final void main(String[] args) {
。。。
//輪詢器的初始化
Looper.prepareMainLooper();
。。。
//輪詢器開始取訊息
Looper.loop();
}
其中 , prepareMainLooper()裡面實質是呼叫了prepare()方法
public static void prepare() {
prepare(true);
}
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.set()方法添加了一個新的Looper物件 , 也就是說 , 在建立Handler之前 , UI執行緒中就已經呼叫了Looper.prepare()和Looper.loop()方法. 在看Looper.loop()方法之前 , 我們先看一下Looper的構造方法 , 因為在主執行緒中已經建立了一個Looper物件
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
從上面的建構函式中可以看到 , Looper在初始化時 , 就預設建立了一個訊息佇列物件MessageQueue.
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
那我們再來看一下Looper.loop()方法 , 輪詢取訊息了 , 不過還沒有傳送訊息 , 這時是取不到訊息的
public static void loop() {
// 當前是主執行緒,只有一個Looper物件,同handler一起共享
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 與handler共享同一個訊息佇列
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
// 核心程式碼,無限迴圈取訊息了,如果沒有訊息,系統有可能阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
程式碼比較多 , 其實核心程式碼就幾個 , 其中Message msg = queue.next() , 從系統執行開始就一直無限迴圈取訊息 , 所以當訊息為null時 , 可能會造成阻塞. 那我們來看一下next()方法裡是什麼
Message next() {
...
// 程式碼太多,但是看到這一行程式碼就足夠了
nativePollOnce(ptr, nextPollTimeoutMillis);
...
}
這裡就涉及到JNI機制了 , 底層的通訊交給原生代碼了
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr); // 喚醒
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
這裡擴充套件一下 , inux程序間的通訊,linux管道通訊,管道其實就是個特殊的檔案,該檔案有兩個描述符(操作檔案的控制代碼(引用)),讀的描述符,寫的描述符
通訊的原型
主執行緒拿著讀的描述符等待讀取資料,子執行緒拿著寫的描述符開始寫資料,寫完資料之後通知拿著讀描述符的主執行緒,主執行緒喚醒,開始輪詢取訊息,處理訊息,如果沒有訊息中斷(阻塞)
(3) 傳送訊息
呼叫handle.sendMessage(msg);
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// handler傳送訊息,把當前的handler物件繫結到message物件中
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 把訊息放到訊息佇列裡,並排序
return queue.enqueueMessage(msg, uptimeMillis);
}
感覺發訊息發了好久 , 上面的sendMessageDelayed()方法可以延遲發訊息 , 最終實質上是呼叫了queue.enqueueMessage(msg, uptimeMillis)方法 , 把訊息放到訊息佇列裡,並排序.
boolean enqueueMessage(Message msg, long when) {
// this表示同一個容器,訊息佇列
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) {
// 喚醒jni
nativeWake(mPtr);
}
}
return true;
}
(4) 取訊息
又回到上面的Looper.loop()方法了 , 精簡版
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
// 取訊息
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
// 處理資訊
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
走到這一步了 , 讓我們來看看handler最後是如何處理訊息的 , msg.target.dispatchMessage(msg) , 注意的是 , 該方法是在主執行緒中執行的 , 因為loop()方法就是在主執行緒中執行
public void dispatchMessage(Message msg) {
// 傳送資訊時,預設msg.callback為null
if (msg.callback != null) {
// 處理runOnUiThread方法
handleCallback(msg);
} else {
// 我們走這裡,msg.callback==null
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 主執行緒,方法回撥
handleMessage(msg);
}
}
最後回撥給了主執行緒中的handler的handleMessage()方法 , 這就是Handler非同步訊息處理機制的全過程.
6. runOnUiThread方法的原始碼分析
實質上也是呼叫了Handler的post方法
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
// 關鍵點
mHandler.post(action);
} else {
action.run();
}
}
接下來我們深入看下Handler的post方法的程式碼邏輯
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
看到sendMessageDelayed()方法是不是有種很親切的感覺 , 跟上面不同 , 此時傳入的第一個引數msg為getPostMessage(r)
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
那麼 , 重點來了 , 返回一個新的Message物件 , 只是多添加了一個callback屬性 , callback不再為null , 走到最後的handler.dispatchMessage(msg)方法
public void dispatchMessage(Message msg) {
// 此時,msg.callback不為null
if (msg.callback != null) {
// 處理runOnUiThread方法
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
此時 , msg.callback不為null , 那麼就走handleCallback(msg)方法
private static void handleCallback(Message message) {
message.callback.run();
}
這裡的message.callback就是傳入的Runnable , 它本身跟執行緒是沒有任何關係的 , 之所以會在主執行緒中執行 , 完全是因為dispatchMessage()方法是在主執行緒中執行的 , 然後呼叫Runnable.run()方法.
歡迎大家交流 , 共同探討!