寫在前面的話
提起安卓的消息機(jī)制挽鞠,我們馬上就會聯(lián)想到Handler媚赖,而Handler在日常的開發(fā)中經(jīng)常會用到查排,因此了解安卓的消息機(jī)制還是很有必要的凳枝,畢竟知己知彼,百戰(zhàn)不殆跋核。
所謂的消息機(jī)制,實質(zhì)上是線程之間通信的一種機(jī)制叛买。在平常的開發(fā)中砂代,我們都知道子線程中不能更新UI,我們的做法就是在子線程要更新UI的地方通知主線程率挣,讓主線程完成UI的更新刻伊。
與消息機(jī)制相關(guān)的類
Handler
負(fù)責(zé)發(fā)送和接收消息
Message
消息的載體
MessageQueue
消息隊列
Looper
負(fù)責(zé)循環(huán)消息隊列
ThreadLocal<T>
線程內(nèi)部數(shù)據(jù)存儲類,ThreadLocal通過set方法存儲數(shù)據(jù)椒功,通過get方法獲取數(shù)據(jù)捶箱。在消息機(jī)制中,就是通過它來存儲每一個線程的Looper對象
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
消息機(jī)制具體流程
接下來动漾,我就以子線程如何通知主線程更新UI這一例子來詳細(xì)介紹一下安卓的消息機(jī)制丁屎。
1. 調(diào)用Looper.prepare方法
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
由代碼可知,在prepare方法中旱眯,會創(chuàng)建一個Looper對象晨川,并且一個線程也只會創(chuàng)建一個证九。
同時在Looper的構(gòu)造方法中,會創(chuàng)建一個消息隊列共虑,即MessageQueue愧怜。
2. 封裝一條需要發(fā)送的消息
Message msg = Message.obtain();
msg.what = 0;
msg.obj= obj;
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();
}
創(chuàng)建消息,我們用obtain方法妈拌,該方法的原則是拥坛,如果消息池中有Message,則直接取出尘分,沒有才會新創(chuàng)建一個Message猜惋。
what,消息的標(biāo)記音诫,類型為int
obj惨奕,消息需要傳遞的對象,類型為Object
3. 調(diào)用Handler的 send或者post 的方法發(fā)送消息
3.1首先創(chuàng)建一個Handler對象mHandler
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
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;
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在Handler的構(gòu)造方法竭钝,我們可以看到梨撞,mHandler與Looper和消息隊列建立了關(guān)聯(lián)
3.2 調(diào)用send或者post方法
send
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long delayMillis)
sendEmptyMessageAtTime(int what, long uptimeMillis)
sendMessage(Message msg)
sendMessageDelayed(Message msg, long delayMillis)
sendMessageAtTime(Message msg, long uptimeMillis)
sendMessageAtFrontOfQueue(Message msg)post
post(Runnable r)
postDelayed(Runnable r, long delayMillis)
postAtTime(Runnable r, long uptimeMillis)
postAtTime(Runnable r, Object token, long uptimeMillis)
postAtFrontOfQueue(Runnable r)
經(jīng)過查看post方法的源碼,發(fā)現(xiàn)post方法實際上也是調(diào)用的send類的方法在發(fā)送消息香罐,區(qū)別在于post方法的參數(shù)是Runnable卧波。
下面是post方法相關(guān)的源碼
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
看上面代碼我們知道,post方法中傳遞的參數(shù)雖然不是Message庇茫,但最終傳遞的對象依然是Message港粱,Runnable對象成為了這個消息的一個屬性
通過對Handler源碼的分析,發(fā)現(xiàn)除了sendMessageAtFrontOfQueue方法之外旦签,其余任何send的相關(guān)方法查坪,都經(jīng)過層層包裝走到了sendMessageAtTime方法中,我們來看看源碼:
(實際上宁炫,sendMessageAtFrontOfQueue方法除了uptimeMillis為0外偿曙,和sendMessageAtTime 一模一樣)
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);
}
此時,mHandler會將消息通過enqueueMessage方法羔巢,放入消息隊列
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
msg.target = this望忆,就相當(dāng)于給該消息貼上了mHandler的標(biāo)簽(誰發(fā)送的消息,誰接收處理)
這里的enqueueMessage方法是MessageQueue的方法竿秆,在該方法中會將Message根據(jù)時間排序启摄,放入到消息隊列中。
4. 調(diào)用Looper.loop方法
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
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.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
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);
}
msg.recycleUnchecked();
}
}
在這個方法中幽钢,有一個for的死循環(huán)歉备,不斷地調(diào)用queue.next()方法,將Message從消息隊列中取出
然后調(diào)用msg.target.dispatchMessage(msg)方法搅吁,msg.target實際上就是mHandler
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
分析這個方法威创,有兩個分支handleCallback和handleMessage落午,回憶前面所說的send和post方法,當(dāng)調(diào)用的是send類的方法時肚豺,明顯走h(yuǎn)andleMessage這個分支溃斋,此時,子線程已經(jīng)成功將消息傳遞至主線程吸申,在這里我們就可以更新UI了
當(dāng)調(diào)用的是post方法時梗劫,msg.callback就是Runnable對象,此時會走h(yuǎn)andleCallback分支
private static void handleCallback(Message message) {
message.callback.run();
}
此時調(diào)用了run方法截碴,走到這梳侨,子線程也已經(jīng)將消息成功傳至主線程,在這里我們就可以更新UI了
總結(jié)一下
任何線程在用到Handler處理消息時日丹,都需要經(jīng)過上面說的4個步驟走哺,缺一不可,具體代碼如下
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
一個線程只有一個Looper,一個消息隊列
Handler在什么線程創(chuàng)建實例哲虾,這個Handler就屬于該線程
順便提一下在子線程中更新UI的方法
1.handler.sendMessage
2.handler.post
3.view.post
4.activity.runOnUiThread
view.post
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;
}
activity.runOnUiThread
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
查看源碼發(fā)現(xiàn)丙躏,實際上2,3束凑,4的原理和1是一樣的晒旅,都是利用Handler來發(fā)送消息。
有人會疑問汪诉,我們平時在用Handler解決子線程不能更新UI的問題時废恋,只是在主線程中創(chuàng)建了一個Handler對象,然后在子線程用這個Handler對象發(fā)送了一個消息扒寄,最后在Handler的回調(diào)方法中成功更新了UI鱼鼓,并沒有經(jīng)過1和4兩個步驟。實際上在主線程中该编,1和4兩個步驟蚓哩,系統(tǒng)已經(jīng)幫我們做了,下面是ActivityThread的main方法
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>");
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");
}