帶你深入理解android Handler機制
歡迎轉載請註明來源
說到消息機制,我們一定會想到Handler,由於Android系統規定主線程不能阻塞超過5s,否則會出現”Application Not Responding”。也就是說,你不能在主線程中進行耗時操作(網絡請求,數據庫操作等),只能在子線程中進行。下面先來看一下在子線程中訪問UI會出現什麽情況。
public void click(View v){ new Thread(new Runnable() { @Override public void run() { mTextView.setText("2"); } }) .start(); }
結果不出意外的報錯:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
在ViewRootImpl的checkThread()檢查是否是主線程,如果不是拋異常。
那麽這個時候怎麽解決才能在更新UI呢?其實就是用Handler機制啦!
Handler
先來看一下如何改進代碼,然後詳細分析Handler機制。
public void click(View v){ new Thread(new Runnable() { @Override public void run() { //拿到message對象 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)開始說起吧。當我們調用sendMessage時候,其實最終調用的是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); }
它會調用enqueueMessage()將Message送到MessageQueue中去。那麽MessageQueue是什麽呢?顧名思義是消息隊列,其實在我們創建Handler的時候,它需要與Looper作關聯,Looper類有一個成員變量
MessageQueue mQueue,它就是消息隊列。用來接收Handler發送Message。MessageQueue內部並不是用數組存儲的,而是用鏈表的數據結構,方便添加和刪除。
下面來看一下Looper.looper()源碼,這個方法就是將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對象是否為空,如果是空,拋出”No Looper; Looper.prepare() wasn’t called on this thread.”異常。換句話說,如果我們在子線程中創建Handler,並調用sendMessage()時候,由於沒有Looper對象,就會拋此異常信息。我們可以通過Looper.prepare()將當前線程轉為Looper線程。該源碼會在下面分析。
主要看 for (;;)那段代碼,它是個死循環,不斷地執行next()方法,如果有新消息,就交給 msg.target.dispatchMessage(msg);這裏msg.target其實就是Handler對象。那麽下面我們看一下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); } }
其實看到這裏就明白了,它調用的就是handleMessage(),所以我們就可以輕松的更新UI界面了!
那麽mCallback.handleMessage(msg)是什麽呢?
接下來我們看一下這個代碼:
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,結果吐司展現1,展現完之後再展示2.
當return true時,結果吐司只展現1。這樣我們就可以知道,這其實是用來攔截處理消息的。
剛剛提到Looper.prepare()可以將當前線程轉為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)); }
通過拋出的那個異常我們可以發現一個Handler只能有一個Looper.而一個Looper內部維護者MessageQueue,當有消息時Looper從MessageQueue中取出消息交給Handler處理。這樣它們之間就建立起關系了。
看一下源碼中的這行代碼 sThreadLocal.set(new Looper(quitAllowed)); 關於ThreadLocal可以看一下我的這篇文章
http://blog.csdn.net/qq_31609983/article/details/52094032
要想到Looper不斷的從MessageQueue中取消息,就必須調用Looper.loop()來不斷取消息.
在子線程中發送消息的完整代碼如下:
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();這二個方法順序不能變,我們可以看一下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; }
註意這段代碼 mLooper = Looper.myLooper();如果為空,拋異常,該異常意思是必須調用Looper.prepare().這就是說為什麽順序不能改變!
那麽讀者可能會問,我們在主線程創建Handler對象,並沒有調用Looper.prepare()也出什麽問題啊,的確是這樣的,因為ActivityThread 的main()函數裏面 Looper.prepareMainLooper();已經自動幫我們創建好了Looper對象了。看一下源碼:
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(); }
講了這麽多,估計對於小白來說有點暈了。哈哈。那麽接下來我盜用一張hyman老師的一張圖分析Handler,MessageQueue,Looper之間的關系吧。。。
0160904160222603)
一天,老板正在和員工開會,員工想上廁所,出於禮貌,對老板說我要去上廁所(sendMessage 到 MessageQueue中) 老板思考了一會兒,回復到”你去吧”(Looper.loop()) , 最後員工去WC了(handleMessage) ,從詢問到最後WC都是員工做的事,這就是它們之間的關系了。。。哈哈哈。
其實Handler發送消息有多種方式
- msg.sendToTarget();
- mHandler.sendMessageAtFrontOfQueue();
- mHandler.sendEmptyMessage()
- mHandler.sendEmptyMessageDelayed()
- mHandler.post()
- mHandler.postDelayed()
雖然有多種方法,但本質都是通過Handler.handleMessage()實現的。
還有幾個這裏就不一一列舉了,有興趣的讀者可以去Android官網吧。。。
聲明
感謝《Android開發藝術探索》,感謝慕課網視頻,感謝郭神。
我來附一下鏈接吧,感興趣的話去看一下吧。。。
視頻:
http://www.imooc.com/learn/267
郭神博客:
http://blog.csdn.net/guolin_blog/article/details/9991569
最後:這是筆者第一次寫分析源碼的文章,(估計也是最後一次了吧。。。哈哈哈。。),寫的不好,比較雜亂,還請讀者多多包涵。寫著寫著就過了24點了,即將迎來大三,希望用這篇文章來為準大三生活開個好頭吧!祝大家生活愉快!
Tags: Android 數據庫操作 original private created
文章來源: