說到消息機(jī)制,我們一定會(huì)想到Handler,由于Android系統(tǒng)規(guī)定主線程不能阻塞超過5s,否則會(huì)出現(xiàn)"Application Not Responding"。也就是說,你不能在主線程中進(jìn)行耗時(shí)操作(網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫操作等),只能在子線程中進(jìn)行。下面先來看一下在子線程中訪問UI會(huì)出現(xiàn)什么情況。
public void click(View v){
new Thread(new Runnable() {
@Override
public void run() {
mTextView.setText("2");
}
}) .start();
}
結(jié)果不出意外的報(bào)錯(cuò):
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.###
在ViewRootImpl的checkThread()檢查是否是主線程,如果不是拋異常。
那么這個(gè)時(shí)候怎么解決才能在更新UI呢?其實(shí)就是用Handler機(jī)制啦!
Handler##
先來看一下如何改進(jìn)代碼,然后詳細(xì)分析Handler機(jī)制。
public void click(View v){
new Thread(new Runnable() {
@Override
public void run() {
//拿到Message對(duì)象
Message msg = mHandler.obtainMessage();
msg.arg1 = 2;
mHandler.sendMessage(msg);
}
}) .start();
}
然后在handleMessage中更新UI
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mTextView.setText(msg.arg1+"");
}
};
這樣就成功了。
僅僅知道怎么用那肯定是不夠的,我們還需要知道其背后到底干了什么。
我們就從 mHandler.sendMessage(msg)開始說起吧。當(dāng)我們調(diào)用sendMessage時(shí)候,其實(shí)最終調(diào)用的是sendMessageAtTime(msg,long)。此方法源碼如下:
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);
}
它會(huì)調(diào)用enqueueMessage()將Message送到MessageQueue中去。那么MessageQueue是什么呢?顧名思義是消息隊(duì)列,其實(shí)在我們創(chuàng)建Handler的時(shí)候,它需要與Looper作關(guān)聯(lián),Looper類有一個(gè)成員變量
MessageQueue mQueue,它就是消息隊(duì)列。用來接收Handler發(fā)送Message。MessageQueue內(nèi)部并不是用數(shù)組存儲(chǔ)的,而是用鏈表的數(shù)據(jù)結(jié)構(gòu),方便添加和刪除。
下面來看一下Looper.looper()源碼,這個(gè)方法就是將Message交給Handler.handleMessage去完成的。
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.recycleUnchecked();
}
}
第3,4行:判斷Looper對(duì)象是否為空,如果是空,拋出"No Looper; Looper.prepare() wasn't called on this thread."異常。換句話說,如果我們?cè)谧泳€程中創(chuàng)建Handler,并調(diào)用sendMessage()時(shí)候,由于沒有Looper對(duì)象,就會(huì)拋此異常信息。我們可以通過Looper.prepare()將當(dāng)前線程轉(zhuǎn)為Looper線程。該源碼會(huì)在下面分析。
主要看 for (;;)那段代碼,它是個(gè)死循環(huán),不斷地執(zhí)行next()方法,如果有新消息,就交給 msg.target.dispatchMessage(msg);這里msg.target其實(shí)就是Handler對(duì)象。那么下面我們看一下Handler.dispatchMessage(msg)到底干了什么。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其實(shí)看到這里就明白了,它調(diào)用的就是handleMessage(),所以我們就可以輕松的更新UI界面了!
那么mCallback.handleMessage(msg)是什么呢?
接下來我們看一下這個(gè)代碼:
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(getApplicationContext(),"1",Toast.LENGTH_SHORT).show();
return false;
}
}){
@Override
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(),"2",Toast.LENGTH_SHORT).show();
}
};
注意:在第5行我return false,結(jié)果吐司展現(xiàn)1,展現(xiàn)完之后再展示2.
當(dāng)return true時(shí),結(jié)果吐司只展現(xiàn)1。這樣我們就可以知道,這其實(shí)是用來攔截處理消息的。
剛剛提到Looper.prepare()可以將當(dāng)前線程轉(zhuǎn)為Looper線程。那看一下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));
}
通過拋出的那個(gè)異常我們可以發(fā)現(xiàn)一個(gè)Handler只能有一個(gè)Looper.而一個(gè)Looper內(nèi)部維護(hù)者M(jìn)essageQueue,當(dāng)有消息時(shí)Looper從MessageQueue中取出消息交給Handler處理。這樣它們之間就建立起關(guān)系了。
看一下源碼中的這行代碼 sThreadLocal.set(new Looper(quitAllowed)); 關(guān)于ThreadLocal可以看一下我的這篇文章
http://www.itdecent.cn/writer#/notebooks/4409171/notes/5075332
要想到Looper不斷的從MessageQueue中取消息,就必須調(diào)用Looper.loop()來不斷取消息.
在子線程中發(fā)送消息的完整代碼如下:
public void click(View v){
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler.sendMessage();
Looper.loop();
}
}) .start();
}
注意 Looper.prepare(); handler.sendMessage();這二個(gè)方法順序不能變,我們可以看一下Handler構(gòu)造方法
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;
}
注意這段代碼 mLooper = Looper.myLooper();如果為空,拋異常,該異常意思是必須調(diào)用Looper.prepare().這就是說為什么順序不能改變!
那么讀者可能會(huì)問,我們?cè)谥骶€程創(chuàng)建Handler對(duì)象,并沒有調(diào)用Looper.prepare()也出什么問題啊,的確是這樣的,因?yàn)锳ctivityThread 的main()函數(shù)里面 Looper.prepareMainLooper();已經(jīng)自動(dòng)幫我們創(chuàng)建好了Looper對(duì)象了??匆幌略创a:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
注意sMainLooper = myLooper();看一下myLooper()方法:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
講了這么多,估計(jì)對(duì)于小白來說有點(diǎn)暈了。哈哈。那么接下來我盜用一張hyman老師的一張圖分析Handler,MessageQueue,Looper之間的關(guān)系吧。。。
0160904160222603)
一天,老板正在和員工開會(huì),員工想上廁所,出于禮貌,對(duì)老板說我要去上廁所(sendMessage 到 MessageQueue中) 老板思考了一會(huì)兒,回復(fù)到"你去吧"(Looper.loop()) , 最后員工去WC了(handleMessage) ,從詢問到最后WC都是員工做的事,這就是它們之間的關(guān)系了。。。哈哈哈。
其實(shí)Handler發(fā)送消息有多種方式
- msg.sendToTarget();
- mHandler.sendMessageAtFrontOfQueue();
- mHandler.sendEmptyMessage()
- mHandler.sendEmptyMessageDelayed()
- mHandler.post()
- mHandler.postDelayed()
雖然有多種方法,但本質(zhì)都是通過Handler.handleMessage()實(shí)現(xiàn)的。
還有幾個(gè)這里就不一一列舉了,有興趣的讀者可以去Android官網(wǎng)吧。。。
聲明##
感謝《Android開發(fā)藝術(shù)探索》,感謝慕課網(wǎng)視頻,感謝郭神。
我來附一下鏈接吧,感興趣的話去看一下吧。。。
視頻:
郭神博客:
最后:這是筆者第一次寫分析源碼的文章,(估計(jì)也是最后一次了吧。。。哈哈哈。。),寫的不好,比較雜亂,還請(qǐng)讀者多多包涵。寫著寫著就過了24點(diǎn)了,即將迎來大三,希望用這篇文章來為準(zhǔn)大三生活開個(gè)好頭吧!祝大家生活愉快!