android 異步通信機制 Handler 的分析與運用
當我們應用程序啟動時,Android系統就會創建一個主線程即UI線程,在這個UI線程中進行對UI控件的管理,如頁面的刷新或者事件的響應等過程。同時Android規定在UI主線程不能進行耗時操作,否則會出現ANR現象,對此,我們一般是通過開啟子線程來進行耗時操作,在子線程中通常會涉及到頁面的刷新問題,這就是如何在子線程進行UI更新,對于這個問題,我們一般通過異步線程通信機制中的Handler來解決,接下來我們就來分析一下Handler機制。
常規用法
public class MainActivity extends AppCompatActivity {
@BindView(R.id.execute)
Button execute;
@BindView(R.id.text)
TextView text;
//處理子線程發過來的消息
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
//子線程發送消息
new Thread(new Runnable() {
@Override
public void run() {
Message message = handler.obtainMessage();
message.what = 1;
message.obj = "子線程更新UI操作";
handler.sendMessage(message);
}
}).start();
}
}
以上代碼就是我們一般會使用到的,子線程通過Message,給主線程發送消息進行UI操作,接下來我們就一步一步進行深究,看看android是如何實現子線程和主線程如何交互的。
首先,我們在主線程中開啟一個子線程,我們用了以下方式:
new Thread(new Runnable() {
@Override
public void run() {
//處理事件
}
}).start();
開啟一個線程,通常有兩種方式:
- 繼承Thread類,覆蓋run方法
- 實現runnable接口,實現run方法
對于第一種方法 繼承Thread類,覆蓋run方法 ,我們查看源碼就可以知道,最終還是實現runnable接口,所以沒有多大的區別。
public
class Thread implements Runnable {
//...
}
回歸正題:
Message message = handler.obtainMessage();
message.what = 1;
message.obj = "子線程更新UI操作";
handler.sendMessage(message);
我們在run方法中進行發送消息,對于第一行我們獲得一個消息是通過
handler.obtainMessage();
而不是通過
Message message =new Message();
這兩者有什么區別呢?還是來進入到源碼中一窺究竟吧!我們首先進入Handler類中,進行查看
public final Message obtainMessage()
{
return Message.obtain(this);
}
繼續進入
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
最終來到了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();
}
我們仔細觀察一下sPool ,這個sPool 是什么東西呢?pool是池的意思,線程有線程池,那么我們也可以認為Message也有一個對象池,我們分析一下源碼可以得知:
如果Message對象池中還存在message的話,我們直接使用message,而不是創建一個新的Message
接下來就是對message進行一些常規的設置,如要傳遞的消息內容之類的,最后進行消息 的發送。
我們進入到消息的最后一步源碼中進行查看:
handler.sendMessage(message);
會調用sendMessageDelayed方法
//Handler類
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
繼續深入查看
//Handler.java
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
//定時發送消息
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
如果我們設置了延時時間,那么會計算相應的發送時間,當前時間加上延時就是最終的消息發送時間。
//Handler.java 定時發送消息
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實際上是Handler
msg.target = this;
//異步
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//消息入隊
return queue.enqueueMessage(msg, uptimeMillis);
}
我們來看看大頭,消息是如何入隊的。
//MessageQueue.java 消息入隊,隊列的實現其實單鏈表的插入和刪除操作
boolean enqueueMessage(Message msg, long when) {
//指的是Handler
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) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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;
}
當Handler將消息插入到消息隊列后,那么重要的問題來了,子線程是如何和主線程通信的呢?按道理講,既然可以將插入到隊列中,那么肯定有一個東西可以從消息隊列中去消息然后進行處理,對于這個東西,就是有Looper來承擔了。
我們首先來看下Looper這個類:
public final class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
//用于存放當前線程的looper對象
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//主線程的Looper也就是UI線程Looper
private static Looper sMainLooper; // guarded by Looper.class
//當前線程的消息隊列
final MessageQueue mQueue;
//當前線程
final Thread mThread;
//...
}
我們對Looper這個類進行了簡單的介紹,對于消息的獲取并處理我們得進入到主線程中即ActivityThread.java類中去
public static void main(String[] args) {
//省略部分代碼...
Looper.prepareMainLooper(); ------------------(1)
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
//省略部分代碼...
Looper.loop(); ------------------------(2)
throw new RuntimeException("Main thread loop unexpectedly exited");
}
對于 Looper.prepareMainLooper() 我們進行分析看看,到底是什么?
public static void prepareMainLooper() {
//不允許退出
prepare(false);
synchronized (Looper.class) {
//一個線程只能有一個Looper對象
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//主線程的looper
sMainLooper = myLooper();
}
}
對于myLoop()是什么東東?
/**
* 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();
}
是我們一開始介紹的looper類中的相關變量,也就是存儲Looper對象的東西,類似于一個容器。這里是取的Looper對象,那么我們在哪里進行存呢?我們進入到prepare方法中:
//保存當前線程的Looper對象
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));
}
接下來我們看一下 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();
//主線程中不需要手動調用Looper.prepare()方法,
//當我們使用子線程時需要手動調用Looper.prepare()方法,否則會報異常。
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) {
// No message indicates that the message queue is quitting.
return;
}
//省略部分代碼...
try {
//msg.target就是Handler,Handler處理消息
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略部分代碼...
//消息回收
msg.recycleUnchecked();
}
}
Looper.loop()其實就是不斷的從隊列中獲取消息,然后進行處理。
在上面方法中,有一行代碼:msg.target.dispatchMessage(msg);我們進入內部去詳細查看一下實現:
//Handler.java 分發消息
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
// msg.callback== Runnable callback;
if (msg.callback != null) {
//如果有runnable,那么則實現它的run方法里面的內容
handleCallback(msg);
} else {
//否則處理handleMessage方法
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handleCallback(msg);方法實現
private static void handleCallback(Message message) {
//msg.callback== Runnable callback;
message.callback.run();
}
handleMessage方法實現
public interface Callback {
public boolean handleMessage(Message msg);
}
對于上面那個方法,其實就是我們在主線程中實現的方法:
//消息處理
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);
}
}
};
到此,基本上就已經分析了大概,不過,我們在實際的開發過程中有時候會碰到這個問題:
Can't create handler inside thread that has not called Looper.prepare()
而我們的代碼是如何寫的呢?
//自定義一個Thread繼承自Thread
private class MyThread extends Thread {
private Handler myThreadHandler;
@Override
public void run() {
myThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
//todo...
}
}
};
Message message = new Message();
message.what = 1;
message.obj = "MyThread Message Handler";
myThreadHandler.sendMessage(message);
}
}
上述代碼很簡單,就是自定義一個Thread,然后申明一個Handler來使用,然后通過下面代碼進行線程通信:
myThreadBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new MyThread().start();
}
});
為什么上面簡單的代碼會出現這個問題呢?而我們在Activity中申明的Handler就可以直接用,而不會出現上述的error?其實,我們在UI主線程中使用Handler時已經調用過了Looper.prepare()和Looper.loop(),我們返回到上面的 ActivityThread.java 類中的main方法處,我們可以發現,其實UI主線程已經調用過了,
Looper.prepareMainLooper();
//...
Looper.loop();
而在我們子線程卻需要我們自己手動調用一下,知道了原因所在,我們來修改一下,再次運行,即可得出正確的答案。
private class MyThread extends Thread {
private Handler myThreadHandler;
@Override
public void run() {
//注意此處的prepare方法
Looper.prepare();
myThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
//todo...
}
}
};
Message message = new Message();
message.what = 1;
message.obj = "MyThread Message Handler";
myThreadHandler.sendMessage(message);
//注意此處的loop方法
Looper.loop();
}
}
以上修改內容即可得出正確的答案,接下來我們來總結一下:
為什么使用異步消息處理的方式就可以對UI進行操作了呢?這是由于Handler總是依附于創建時所在的線程,比如我們的Handler是在主線程中創建的,而在子線程中又無法直接對UI進行操作,于是我們就通過一系列的發送消息、入隊、出隊等環節,最后調用到了Handler的handleMessage()方法中,這時的handleMessage()方法已經是在主線程中運行的,因而我們當然可以在這里進行UI操作了。
除了通過Handler的sendMessage方法來進行子線程和主線程進行通信外,我們還可以通過以下的方法來達到相同的效果。
1、handler.post
我們來仔細分析一下源代碼進行說明。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
調用handler.post方法,將runnable參數轉化為一條message進行發送的。接著我們進入getPostMessage(r)中進行分析看看。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
將傳遞進來的runnable參數賦值給Message的callback變量,賦值給它有什么用呢?我們還記不記得在Handler的dispatchMessage時會做一個判斷???
public void dispatchMessage(Message msg) {
//判斷Message的callback是否為null,這里的callback就是runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
上面的callback是否為null的判斷決定著整個流程,如果callback不等于null的話我們進入handleCallback(msg)方法中一窺究竟。
private static void handleCallback(Message message) {
message.callback.run();
}
看到沒?直接調用了run方法,其中的message.callback就是Runnable,所以它會直接執行run方法。所以對于在子線程中更新UI時使用handler.post 方法時,直接在run方法中進行UI更新如:
mHandler.post(new Runnable() {
@Override
public void run() {
handlerText.setText("result: this is post method");
}
});
2、view.post
同理,我們也是進入到源碼中進行分析一下:
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action 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.
*
* @see #postDelayed
* @see #removeCallbacks
*/
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;
}
通過該方法的注釋我們就知道,首先會將runnable放進到Message隊列中去,然后在UI主線程中運行,調用handler的post方法,本質的原理都是一樣的。
3、runOnUiThread
對于runOnUiThread方法,我們從字面上也可以了解到是在主線程中運行的,我們詳細分析一下:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
代碼很簡單,就是先判斷一下當前線程是否是UI主線程,是的話,直接運行run方法,不是的話通過Handler來實現。
通過以上四種在子線程中更新UI的方法,其內在的本質都是一樣的,都是借助于Handler來實現的,本篇分析了異步通信機制中的Handler,通過本篇的學習與了解,對于實際項目中如果遇到相似的問題的話,我想應該可以迎刃而解了。 知其然而知所以然!
最后我們來總結一下本篇文章中涉及到的各個對象的意思:
1、MessageQueue
消息隊列,它的內部存儲了一組數據,以隊列的形式向外提供了插入和刪除的工作。但是它的內部實現并不是隊列,而是 單鏈表 。
2、Looper
會不停檢查是否有新的消息,如果有就調用最終消息中的Runnable或者Handler的handleMessage方法。對應提取并處理消息。
3、Handler
Handler的工作主要包含消息的發送和接收過程。消息的發送可以通過post的一系列方法以及send的一系列方法來實現,不過最后都是通過send的一系列方法實現的。對應添加消息和處理線程。
4、Message
封裝了需要傳遞的消息,并且本身可以作為鏈表的一個節點,方便MessageQueue的存儲。
5、ThreadLocal
一個線程內部的數據存儲類,通過它可以在指定的線程中儲存數據,而其它線程無法獲取到。在Looper、AMS中都有使用。
參考
1、http://www.jianshu.com/p/94ec12462e4e)
2、http://blog.csdn.net/woshiwxw765/article/details/38146185
關于作者
1. 簡書 http://www.jianshu.com/users/18281bdb07ce/latest_articles
2. 博客 http://crazyandcoder.github.io/
3. github https://github.com/crazyandcoder