當我們應用程式啟動時,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