之前寫了一篇關於Message的文章,感興趣的朋友可以去看一下【Android訊息機制之Message解析(面試)】,這一次我們們來聊一下訊息機制中用得最多的Handler,也是面試中問得最多的之一,在這裡我先拋幾個問題出來:
1.Handler、Looper、Thread有什麼關係?
2.為什麼在子執行緒建立handler會拋異常 "Can't create handler inside thread that has not called Looper.prepare()"?
3.如何使用handler來處理message?
4.為什麼不能在子執行緒更新UI?
我們帶著問題去看原始碼:
大家先對一下的物件,腦補一下工廠的情景:
Handler:訊息的處理者,工廠中流水線的工人。
Message:系統傳遞的訊息,工廠中流水線上的產品。
MessageQueue:訊息佇列,工廠中流水線上的傳送帶。
Looper:發動機,工廠中使流水線的傳送帶運動的發動機。
我們先從程式入口來進行分析,Android應用程式的入口在ActivityThread的main函式中,我們先從main函式進行分析:
ActivityThread.java:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
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>");
//在android應用程式的入口其實在ActivityThread的main方法
//在這裡,主執行緒會建立一個Looper物件。
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");
}
複製程式碼
程式碼很長,我們挑關鍵的程式碼來看,在程式碼中我已寫上註釋,在main函式中,Looper呼叫了prepareMainLooer(),我們再進去Looper看看。
Looper.java:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
//在主執行緒中,其預設初始化一個Looper物件,因此我們在主執行緒的操作中是不需要自己去調prepare()。
prepare(false);
synchronized (Looper.class) {
//這裡先進行判斷,在主執行緒是否已經存在Looper了,
// 避免我們手動去呼叫prepareMainLooper(),因為這個是給程式入口初始化的時候系統會自動呼叫的
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//設定全域性變數,主執行緒的looper
sMainLooper = myLooper();
}
}
複製程式碼
Initialize the current thread as a looper, marking it as an* application's main looper. The main looper for your application* is created by the Android environment, so you should never need* to call this function yourself.
注意這個函式的註釋,大概意思是:在主執行緒建立一個looper,是這個主執行緒的主looper,當這個app在初始化的時候就會自行建立,因此這個函式不是給你們呼叫的,是給系統自身在程式建立的時候呼叫的。
我們繼續往下看,有個prepare(boolean)函式,我們去看看這個到底是用來幹什麼的。 Looper.java:
private static void prepare(boolean quitAllowed) {
//先判斷當前執行緒是否已經存在Looper了,如果存在,不允許設定新的Looper物件,一個執行緒只允許存在一個Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//在當前執行緒中,建立新的Looper物件,並繫結當前執行緒
sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼
在這裡,我們看到了sThreadLocal,我們先看看這個sThreadLocal在Looper是幹什麼用的。
Looper.java:
// sThreadLocal.get() will return null unless you've called prepare().
//sThreadLocal在Looper中作為全域性變數,用於儲存每個執行緒中的資料,可以看做是容器
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
複製程式碼
Looper中,sThreadLocal作為一個全域性變數,sThreadLocal其實是儲存Looper的一個容器,我們繼續往ThreadLocal的get、set進行分析。
ThreadLocal.java:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
//獲取當前執行緒儲存的物件--通過get函式來獲取Looper物件
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
return setInitialValue();
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
//把當前的looper儲存到當前執行緒中
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
複製程式碼
我們看到關鍵的程式碼: Thread t=Thread.currentThread();
也就是說,我們的Looper物件分別儲存在相對應的執行緒中。我們看回來我們的prepare(boolean)函式: looper.java:
private static void prepare(boolean quitAllowed) {
//先判斷當前執行緒是否已經存在Looper了,如果存在,不允許設定新的Looper物件,一個執行緒只允許存在一個Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//在當前執行緒中,建立新的Looper物件,並繫結當前執行緒
sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼
Looper.prepare(boolean)的作用就是建立一個Looper物件,並與當前執行緒繫結在一起。在程式碼中,首先判斷當前執行緒是否已經存在looper,如果不存在則建立新的looper並且繫結到當前的執行緒上。
再看回之前的程式碼: looper.java:
**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
//在主執行緒中,其預設初始化一個Looper物件,因此我們在主執行緒的操作中是不需要自己去調prepare()。
prepare(false);
synchronized (Looper.class) {
//這裡先進行判斷,在主執行緒是否已經存在Looper了,
// 避免我們手動去呼叫prepareMainLooper(),因為這個是給程式入口初始化的時候系統會自動呼叫的
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//設定全域性變數,主執行緒的looper
sMainLooper = myLooper();
}
}
複製程式碼
分別看一下sMainLooper是什麼,myLooper()又是什麼? Looper.java:
//儲存一個主執行緒的looper
private static Looper sMainLooper; // guarded by Looper.class
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
//使用當前執行緒的looper
return sThreadLocal.get();
}
複製程式碼
sMainLooper在Looper做為一個全域性變數,儲存主執行緒繫結的looper,myLooper()則是獲取當前執行緒繫結的Looper。在prepareMainLooper()中,在主執行緒中建立一個新的Looper,並且繫結主執行緒中,同時把這個主執行緒的looper賦值給sMainLooer這個全域性變數。
ActivityThread.java:
public static void main(String[] args) {
......
//在android應用程式的入口其實在ActivityThread的main方法
//在這裡,主執行緒會建立一個Looper物件。
Looper.prepareMainLooper();
......
......
......
//執行訊息迴圈
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
複製程式碼
在應用程式ActivityThread.main入口中,系統除了呼叫Looper.prepareMainLooper,而且在最後還呼叫了Looper.loop(),這個函式有什麼?大家腦補一下,工廠裡的流水線上,除了有傳送帶外,如果你不讓它動起來,那傳送帶也沒什麼作用,那麼Looper.loop的作用就是讓這個傳送帶動起來,也就是我們的讓我們的訊息佇列動起來。
Looper.java:
/**
* 呼叫此函式用於啟動訊息佇列迴圈起來,作用相當於工廠流水線中的傳送帶的開關,
* 只有把開關開啟,傳送帶才跑起來
* 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();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//獲取這個looper的訊息佇列
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;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
//關鍵點,這裡的msg.target也就是hanlder.看回程式碼hanlder.enqueueMessage()
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//最後回收這個message
msg.recycleUnchecked();
}
}
複製程式碼
這一段程式碼比較長,我們挑有中文註釋的來看,先判斷當前的執行緒是否存在looper,如果存在獲取儲存在Looper的訊息佇列messagequeue,然後無限迴圈這個訊息佇列來獲取message,注意我們留到了一段程式碼:
//關鍵點,這裡的msg.target也就是hanlder.看回程式碼hanlder.enqueueMessage()
msg.target.dispatchMessage(msg);
複製程式碼
msg.target其實就是我們的handler,無論是handler通過post或者sendEmptyMessage,最終都會呼叫到調到這個enqueueMessage(),在這裡會將handler賦值到msg.target中.
Handler.java:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//在message中放一個標記
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//在這裡把訊息放到佇列裡面去
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
既然Looper中的loop()呼叫了msg.target.dispatchMessage,我們就看看Handler的dispatchMessage是如何進行處理這個msg的。
Handler.java:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
//這裡先判斷callback是否為空
// callback就是我們使用handler.post(Runnable r)的入參runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果hanlder的入參callback不為空,優先處理
if (mCallback != null) {
//如果回撥返回true.則攔截了handler.handleMessage的方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//這就是為什麼我們使用hanlder的時候,需要重寫handleMessage的方法
handleMessage(msg);
}
}
複製程式碼
在dispatchMessage函式中,意思就是分發這個訊息,在程式碼中先判斷msg.callback是否為空,msg.callback是什麼?在上一篇文章已詳細介紹過了,其實就是handler.post中的runnable物件,通俗的來說就是handler如果有post操作的,就處理post的操作,我們在看看handlerCallback這個函式。 Handler.java:
private static void handleCallback(Message message) {
message.callback.run();
}
複製程式碼
很簡單,就一行程式碼,我們看到了熟悉的run方法,這個不就是我們使用post的時候傳進去的Runnbale物件的run方法嗎?
/**
* 模擬開始
*/
private void doSth() {
//開啟個執行緒,處理複雜的業務業務
new Thread(new Runnable() {
@Override
public void run() {
//模擬很複雜的業務,需要1000ms進行操作的業務
......
handler.post(new Runnable() {
@Override
public void run() {
//在這裡可以更新ui
mTv.setText("在這個點我更新了:" + System.currentTimeMillis());
}
});
}
}).start();
}
複製程式碼
我們回到handler.dispatchMessage(Message)中,如果不是通過post那麼callback就為空,我們看到了一個mCallback變數,我們看看這個Callback的定義:
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
/**
* Constructor associates this handler with the {@link Looper} for the
* current thread and takes a callback interface in which you can handle
* messages.
* <p>
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*
* @param callback The callback interface in which to handle messages, or null.
*/
public Handler(Callback callback) {
this(callback, false);
}
複製程式碼
The callback interface in which to handle messages, or null. 我們可以通過實現這個介面,並作為一個引數傳進去Handler來達到處理這個訊息的效果。
Handler.java:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
//這裡先判斷callback是否為空
// callback就是我們使用handler.post(Runnable r)的入參runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果hanlder的入參callback不為空,優先處理
if (mCallback != null) {
//如果回撥返回true.則攔截了handler.handleMessage的方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//這就是為什麼我們使用hanlder的時候,需要重寫handleMessage的方法
handleMessage(msg);
}
}
複製程式碼
最後一行程式碼中,我們看到了熟悉的handleMessage,這不就是我們經常handler.handlerMessage的方法嗎? Demo:
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//處理訊息
}
};
複製程式碼
但注意之前我們所看到的,如果我們mCallback.handlerMessage(msg)返回為true的話,這樣就不交給handler.handleMessage處理了。
我們繼續看回來我們的Looper.loop() Looper.java:
/**
* 呼叫此函式用於啟動訊息佇列迴圈起來,作用相當於工廠流水線中的傳送帶的開關,
* 只有把開關開啟,傳送帶才跑起來
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
.....
.....
//迴圈通過訊息佇列來獲取訊息
for (; ; ) {
......
//最後回收這個message
msg.recycleUnchecked();
}
}
複製程式碼
在無限迴圈每個訊息的時候,除了呼叫handler.dispatchMessage,最後還會呼叫msg.recycleUnchecked()進行回收這個訊息,至於message怎麼回收我們就不討論了,詳情的大家可以去看看我上一篇文章。
現在我們回過頭看看我之前說的那2個問題:為什麼子執行緒建立handler會拋異常? 我們先看看Handler的關鍵的構造器:
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
* <p>
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
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());
}
}
//獲取當前執行緒的looper
mLooper = Looper.myLooper();
//如果當前執行緒沒有繫結looper則拋異常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製程式碼
看構造器可得,如果在子執行緒建立handler,必須在子執行緒中先建立一個Looper物件,不然hanlder在初始化的時候獲取不了當前執行緒的looper,會丟擲異常"Can't create handler inside thread that has not called Looper.prepare()"。
因此,如果我們在子執行緒中建立Handler的時候,我們可以這樣:
new Thread(new Runnable() {
@Override
public void run() {
//在當前子執行緒建立一個Looper物件
Looper.prepare();
Handler handler=new Handler();
//讓訊息佇列動起來
Looper.loop();
}
}).start();
複製程式碼
這樣就能成功在子執行緒建立handler。
總結:
1.為什麼在主執行緒中建立Handler不需要我們呼叫Looper.prepare().因為在程式的入口中系統會呼叫Looper.prepareMainLooper()來建立,並且讓其主執行緒的Looper啟動起來。如果我們在子執行緒建立handler,需要手動建立looper並且啟動。
2.每一個執行緒只能存在一個Looper, Looper有一個全域性變數sThreadLocal用來儲存每一個執行緒的looper,通過get、set進行存取looper。
3.Handler可以通過通過post或者sendMessage進行傳送訊息,因為其最終會呼叫sendMessageDelayed,我們可以通過runnable方式或者重寫handleMessage進行訊息的處理,當然如果通過handler.sendMessage(msg)的方式的話,我們可以實現Callback介面達到訊息的處理。
4.為什麼不能在子執行緒更新UI?其實更準確的來說應該是UI只能在建立UI的執行緒中進行更新,也就是主執行緒,如果子執行緒建立UI,其可以在子執行緒進行更新。
注: 網上也有很多文章關於Android訊息機制的文章,大神們都寫得很透徹,但為什麼我還要去寫這樣的文章,一方便想通過讀原始碼來提高個人的學習能力,另一方面也想將自己學到的分享給大家,哪怕多年以後看回自己的部落格,也是別有一番滋味。從14年開始出來做android,看了不少文章,學到了很多東西,我很敬佩他們。特別感謝郭霖、李紀鋼、徐宜生等等,因為他們的無私分享讓很多人在工作上解決了不少難題,因此我也希望我能成為這樣的人。