先總結一下Handler,MessageQueue,Looper之間消息傳遞的工作原理和相關異常資訊,後面進行源碼分析。
1 主線程中建立唯一的一個Looper,在Looper對象中,建立MessageQueue對象
首先Android程式啟動時會開啟主線程Main Thread(主線程通常被叫做UI線程);
在Main Thread中會建立Looper對象;
為了保證每個線程中隻能有一個Looper,是以在Looper.prepare()方法中,會判斷目前線程是否已經存在Looper對象,如果存在,則會抛出"Only one Looper may be created per thread",否則就建立Looper,這樣就保證了每個線程中隻能有一個Looper;
在Looper對象中,又會建立MessageQueue;
這樣,和Main Thread相關聯的Looper和MessageQueue就建立好了。
2 Handler 和 Looper與MessageQueue相關聯(都是在主線程中)
當我們在Main Thread中建立Handler時,會去拿到已經建立好的Looper,當然也拿到looper的MessageQueue;
如果為Null,則證明是在非UI線程中,會抛出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()異常;
這樣Handler,Message,Looper三者就相關聯了。
3 Handler,MessageQueue,Looper三者功能區分
Handler發送的消息會發送到MessageQueue中;
Looper.loop方法不斷讀取MessageQueue中的消息,并将消息分發給對應的Handler,handler再處理消息;
MessageQueue為消息隊,由Looper管理;
4 子線程中建立Handler
如果在子線程中建立Handler的時候,在構造Handler的時候,會拿到和目前線程相關聯的Looper對象,但是目前的子線程中并沒有Looper對象,是以會抛出“java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()”異常。
5 在非UI線程更新UI
在非UI線程更新UI時,會調用checkThread方法,判斷目前線程是否是UI線程,如果不是,則會抛出“Only the original thread that created a view hierarchy can touch its views.”即非UI線程不能更新UI。
下面從源碼的角度分析:
6.1 UI線程建立互相關聯的Looper和MessageQueue,并且保證每個線程中隻存在一個Looper對象。
ActivityThread的Main()方法
public static void main(String[] args) {
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());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
其中 Looper.prepareMainLooper()就是建立Looper對象。在prepareMainLooper()中,又會調用 prepare()方法:
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
prepare()方法保證每個一個線程隻能有一個Looper對象。
sThreadLocal即為主線程,初始化的時候 sThreadLocal.get()為空,建立一個Looper()對象。
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
可以看到,在初始化Looper對象的時候,建立了一個與之關聯的MessageQueue()。
這時候,UI線程已經建立了互相關聯的Looper和MessageQueue,并且保證每個線程中隻存在一個Looper對象。
6.2 Handler 和 Looper與MessageQueue相關聯
Handler源碼:
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 = callback;
mAsynchronous = async;
}
當在主線程中建立Handler時,Handler構造函數中會調用Looper.myLooper():
public static Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal即為目前的主線程,通過sThreadLocal.get()拿到目前線程建立的Looper對象。這時候Handler就和Looper相關聯了。
6.3 Handler , Looper,MessageQueue之間的整個消息傳遞機制
當Handler.sendMessage()時, msg.target = this将消息發送的對象指定為目前的Handler。
如果是在子線程中建立Handler,那麼sThreadLocal.get()為空,則會抛出"Can't create handler inside thread that has not called Looper.prepare()"這條剛開始接觸Handler時常見的異常了。
是以:如果要在子線程中建立并使用Handler,則在建立Handler之前,必須調用Looper. prepare()建立一個Looper對象,再調用Looper. loop(),使用死循環不斷取出MessageQueue中的消息。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
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);
}
msg.recycle();
}
}
可以看到, msg.target.dispatchMessage(msg)将取出的消息分發出去, target即為Handler本身。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在dispatchMessage()中,則回調了我們熟悉的 handleMessage(msg)方法。
這樣,就完成了從Handler發送消息到Looper的MessageQueue中,通過Looper.loop()不斷循環,把消息取出來再分發給Handler自己,通過 回調handleMessage(msg),處理不同消息的整個消息傳遞機制。
6.4 為什麼不能在非UI線程更新UI
更改UI時,會調用checkThread()方法,判斷目前線程是否為UI線程,如果不是,則會抛出“Only the original thread that created a view hierarchy can touch its views.”即非UI線程不能更新UI。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
6.5 移除消息隊列裡面的消息
public final void removeCallbacksAndMessages( Object token)
使用場景: 銷毀Activity時,移除隊列中的消息,防止Activity對象不能釋放。