轉載請注明出處:http://blog.csdn.net/guolin_blog/article/details/9991569
之前也是由于周末通宵看TI3比賽,一直沒找到時間寫博客,導致已經有好久沒更新了。慚愧!后面還會恢復進度,盡量保證每周都寫吧。這里也是先恭喜一下來自瑞典的Alliance戰隊奪得了TI3的冠軍,希望明年中國戰隊能夠虎起!
開始進入正題,我們都知道,Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程序就有可能會崩潰。相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也是早已爛熟于心,即創建一個Message對象,然后借助Handler發送出去,之后在Handler的handleMessage()方法中獲得剛才發送的Message對象,然后在這里進行UI操作就不會再出現崩潰了。
這種處理方式被稱為異步消息處理線程,雖然我相信大家都會用,可是你知道它背后的原理是什么樣的嗎?今天我們就來一起深入探究一下Handler和Message背后的秘密。
首先來看一下如何創建Handler對象。你可能會覺得挺納悶的,創建Handler有什么好看的呢,直接new一下不就行了?確實,不過即使只是簡單new一下,還是有不少地方需要注意的,我們嘗試在程序中創建兩個Handler對象,一個在主線程中創建,一個在子線程中創建,代碼如下所示:
[java] view plaincopy
public ?class ?MainActivity?extends ?Activity?{???????? ????private ?Handler?handler1;?? ?????? ????private ?Handler?handler2;?? ?? ????@Override ?? ????protected ?void ?onCreate(Bundle?savedInstanceState)?{?? ????????super .onCreate(savedInstanceState);?? ????????setContentView(R.layout.activity_main);?? ????????handler1?=?new ?Handler();?? ????????new ?Thread(new ?Runnable()?{?? ????????????@Override ?? ????????????public ?void ?run()?{?? ????????????????handler2?=?new ?Handler();?? ????????????}?? ????????}).start();?? ????}?? ?? }??
如果現在運行一下程序,你會發現,在子線程中創建的Handler是會導致程序崩潰的,提示的錯誤信息為?Can't create handler inside thread that has not called Looper.prepare() 。說是不能在沒有調用Looper.prepare() 的線程中創建Handler,那我們嘗試在子線程中先調用一下Looper.prepare()呢,代碼如下所示:
[java] view plaincopy
new ?Thread(new ?Runnable()?{??????@Override ?? ????public ?void ?run()?{?? ????????Looper.prepare();?? ????????handler2?=?new ?Handler();?? ????}?? }).start();??
果然這樣就不會崩潰了,不過只滿足于此顯然是不夠的,我們來看下Handler的源碼,搞清楚為什么不調用Looper.prepare()就不行呢。Handler的無參構造函數如下所示:
[java] view plaincopy
public ?Handler()?{??????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?=?null ;?? }??
可以看到,在第10行調用了Looper.myLooper()方法獲取了一個Looper對象,如果Looper對象為空,則會拋出一個運行時異常,提示的錯誤正是?Can't create handler inside thread that has not called Looper.prepare()!那什么時候Looper對象才可能為空呢?這就要看看Looper.myLooper()中的代碼了,如下所示:
[java] view plaincopy
public ?static ?final ?Looper?myLooper()?{??????return ?(Looper)sThreadLocal.get();?? }??
這個方法非常簡單,就是從sThreadLocal對象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果沒有Looper存在自然就返回空了。因此你可以想象得到是在哪里給sThreadLocal設置Looper了吧,當然是Looper.prepare()方法!我們來看下它的源碼:
[java] view plaincopy
public ?static ?final ?void ?prepare()?{??????if ?(sThreadLocal.get()?!=?null )?{?? ????????throw ?new ?RuntimeException("Only?one?Looper?may?be?created?per?thread" );?? ????}?? ????sThreadLocal.set(new ?Looper());?? }??
可以看到,首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則創建一個新的Looper設置進去。這樣也就完全解釋了為什么我們要先調用Looper.prepare()方法,才能創建Handler對象。同時也可以看出每個線程中最多只會有一個Looper對象。
咦?不對呀!主線程中的Handler也沒有調用Looper.prepare()方法,為什么就沒有崩潰呢?細心的朋友我相信都已經發現了這一點,這是由于在程序啟動的時候,系統已經幫我們自動調用了Looper.prepare()方法。查看ActivityThread中的main()方法,代碼如下所示:
[java] view plaincopy
public ?static ?void ?main(String[]?args)?{??????SamplingProfilerIntegration.start();?? ????CloseGuard.setEnabled(false );?? ????Environment.initForCurrentUser();?? ????EventLogger.setReporter(new ?EventLoggingReporter());?? ????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" );?? }??
可以看到,在第7行調用了Looper.prepareMainLooper()方法,而這個方法又會再去調用Looper.prepare()方法,代碼如下所示:
[java] view plaincopy
public ?static ?final ?void ?prepareMainLooper()?{??????prepare();?? ????setMainLooper(myLooper());?? ????if ?(Process.supportsProcesses())?{?? ????????myLooper().mQueue.mQuitAllowed?=?false ;?? ????}?? }??
因此我們應用程序的主線程中會始終存在一個Looper對象,從而不需要再手動去調用Looper.prepare()方法了。
這樣基本就將Handler的創建過程完全搞明白了,總結一下就是在主線程中可以直接創建Handler對象,而在子線程中需要先調用Looper.prepare()才能創建Handler對象。
看完了如何創建Handler之后,接下來我們看一下如何發送消息,這個流程相信大家也已經非常熟悉了,new出一個Message對象,然后可以使用setData()方法或arg參數等方式為消息攜帶一些數據,再借助Handler將消息發送出去就可以了,示例代碼如下:
[java] view plaincopy
new ?Thread(new ?Runnable()?{??????@Override ?? ????public ?void ?run()?{?? ????????Message?message?=?new ?Message();?? ????????message.arg1?=?1 ;?? ????????Bundle?bundle?=?new ?Bundle();?? ????????bundle.putString("data" ,?"data" );?? ????????message.setData(bundle);?? ????????handler.sendMessage(message);?? ????}?? }).start();??
可是這里Handler到底是把Message發送到哪里去了呢?為什么之后又可以在Handler的handleMessage()方法中重新得到這條Message呢?看來又需要通過閱讀源碼才能解除我們心中的疑惑了,Handler中提供了很多個發送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的發送消息方法最終都會輾轉調用到sendMessageAtTime()方法中,這個方法的源碼如下所示:
[java] view plaincopy
public ?boolean ?sendMessageAtTime(Message?msg,?long ?uptimeMillis)??{?? ????boolean ?sent?=?false ;?? ????MessageQueue?queue?=?mQueue;?? ????if ?(queue?!=?null )?{?? ????????msg.target?=?this ;?? ????????sent?=?queue.enqueueMessage(msg,?uptimeMillis);?? ????}?? ????else ?{?? ????????RuntimeException?e?=?new ?RuntimeException(?? ????????????this ?+?"?sendMessageAtTime()?called?with?no?mQueue" );?? ????????Log.w("Looper" ,?e.getMessage(),?e);?? ????}?? ????return ?sent;?? }??
sendMessageAtTime()方法接收兩個參數,其中msg參數就是我們發送的Message對象,而uptimeMillis參數則表示發送消息的時間,它的值等于自系統開機到當前時間的毫秒數再加上延遲時間,如果你調用的不是sendMessageDelayed()方法,延遲時間就為0,然后將這兩個參數都傳遞到MessageQueue的enqueueMessage()方法中。這個MessageQueue又是什么東西呢?其實從名字上就可以看出了,它是一個消息隊列,用于將所有收到的消息以隊列的形式進行排列,并提供入隊和出隊的方法。這個類是在Looper的構造函數中創建的,因此一個Looper也就對應了一個MessageQueue。
那么enqueueMessage()方法毫無疑問就是入隊的方法了,我們來看下這個方法的源碼:
[java] view plaincopy
final ?boolean ?enqueueMessage(Message?msg,?long ?when)?{??????if ?(msg.when?!=?0 )?{?? ????????throw ?new ?AndroidRuntimeException(msg?+?"?This?message?is?already?in?use." );?? ????}?? ????if ?(msg.target?==?null ?&&?!mQuitAllowed)?{?? ????????throw ?new ?RuntimeException("Main?thread?not?allowed?to?quit" );?? ????}?? ????synchronized ?(this )?{?? ????????if ?(mQuiting)?{?? ????????????RuntimeException?e?=?new ?RuntimeException(msg.target?+?"?sending?message?to?a?Handler?on?a?dead?thread" );?? ????????????Log.w("MessageQueue" ,?e.getMessage(),?e);?? ????????????return ?false ;?? ????????}?else ?if ?(msg.target?==?null )?{?? ????????????mQuiting?=?true ;?? ????????}?? ????????msg.when?=?when;?? ????????Message?p?=?mMessages;?? ????????if ?(p?==?null ?||?when?==?0 ?||?when?<?p.when)?{?? ????????????msg.next?=?p;?? ????????????mMessages?=?msg;?? ????????????this .notify();?? ????????}?else ?{?? ????????????Message?prev?=?null ;?? ????????????while ?(p?!=?null ?&&?p.when?<=?when)?{?? ????????????????prev?=?p;?? ????????????????p?=?p.next;?? ????????????}?? ????????????msg.next?=?prev.next;?? ????????????prev.next?=?msg;?? ????????????this .notify();?? ????????}?? ????}?? ????return ?true ;?? }??
首先你要知道,MessageQueue并沒有使用一個集合把所有的消息都保存起來,它只使用了一個mMessages對象表示當前待處理的消息。然后觀察上面的代碼的16~31行我們就可以看出,所謂的入隊其實就是將所有的消息按時間來進行排序,這個時間當然就是我們剛才介紹的uptimeMillis參數。具體的操作方法就根據時間的順序調用msg.next,從而為每一個消息指定它的下一個消息是什么。當然如果你是通過sendMessageAtFrontOfQueue()方法來發送消息的,它也會調用enqueueMessage()來讓消息入隊,只不過時間為0,這時會把mMessages賦值為新入隊的這條消息,然后將這條消息的next指定為剛才的mMessages,這樣也就完成了添加消息到隊列頭部的操作。
現在入隊操作我們就已經看明白了,那出隊操作是在哪里進行的呢?這個就需要看一看Looper.loop()方法的源碼了,如下所示:
[java] view plaincopy
public ?static ?final ?void ?loop()?{??????Looper?me?=?myLooper();?? ????MessageQueue?queue?=?me.mQueue;?? ????while ?(true )?{?? ????????Message?msg?=?queue.next();??? ????????if ?(msg?!=?null )?{?? ????????????if ?(msg.target?==?null )?{?? ????????????????return ;?? ????????????}?? ????????????if ?(me.mLogging!=?null )?me.mLogging.println(?? ????????????????????">>>>>?Dispatching?to?" ?+?msg.target?+?"?" ?? ????????????????????+?msg.callback?+?":?" ?+?msg.what?? ????????????????????);?? ????????????msg.target.dispatchMessage(msg);?? ????????????if ?(me.mLogging!=?null )?me.mLogging.println(?? ????????????????????"<<<<<?Finished?to????" ?+?msg.target?+?"?" ?? ????????????????????+?msg.callback);?? ????????????msg.recycle();?? ????????}?? ????}?? }??
可以看到,這個方法從第4行開始,進入了一個死循環,然后不斷地調用的MessageQueue的next()方法,我想你已經猜到了,這個next()方法就是消息隊列的出隊方法。不過由于這個方法的代碼稍微有點長,我就不貼出來了,它的簡單邏輯就是如果當前MessageQueue中存在mMessages(即待處理消息),就將這個消息出隊,然后讓下一條消息成為mMessages,否則就進入一個阻塞狀態,一直等到有新的消息入隊。繼續看loop()方法的第14行,每當有一個消息出隊,就將它傳遞到msg.target的dispatchMessage()方法中,那這里msg.target又是什么呢?其實就是Handler啦,你觀察一下上面sendMessageAtTime()方法的第6行就可以看出來了。接下來當然就要看一看Handler中dispatchMessage()方法的源碼了,如下所示:
[java] view plaincopy
public ?void ?dispatchMessage(Message?msg)?{??????if ?(msg.callback?!=?null )?{?? ????????handleCallback(msg);?? ????}?else ?{?? ????????if ?(mCallback?!=?null )?{?? ????????????if ?(mCallback.handleMessage(msg))?{?? ????????????????return ;?? ????????????}?? ????????}?? ????????handleMessage(msg);?? ????}?? }??
在第5行進行判斷,如果mCallback不為空,則調用mCallback的handleMessage()方法,否則直接調用Handler的handleMessage()方法,并將消息對象作為參數傳遞過去。這樣我相信大家就都明白了為什么handleMessage()方法中可以獲取到之前發送的消息了吧!
因此,一個最標準的異步消息處理線程的寫法應該是這樣:
[java] view plaincopy
class ?LooperThread?extends ?Thread?{????????public ?Handler?mHandler;?? ?? ??????public ?void ?run()?{?? ??????????Looper.prepare();?? ?? ??????????mHandler?=?new ?Handler()?{?? ??????????????public ?void ?handleMessage(Message?msg)?{?? ???????????????????? ??????????????}?? ??????????};?? ?? ??????????Looper.loop();?? ??????}?? ??}??
當然,這段代碼是從Android官方文檔上復制的,不過大家現在再來看這段代碼,是不是理解的更加深刻了?
那么我們還是要來繼續分析一下,為什么使用異步消息處理的方式就可以對UI進行操作了呢?這是由于Handler總是依附于創建時所在的線程,比如我們的Handler是在主線程中創建的,而在子線程中又無法直接對UI進行操作,于是我們就通過一系列的發送消息、入隊、出隊等環節,最后調用到了Handler的handleMessage()方法中,這時的handleMessage()方法已經是在主線程中運行的,因而我們當然可以在這里進行UI操作了。整個異步消息處理流程的示意圖如下圖所示:
另外除了發送消息之外,我們還有以下幾種方法可以在子線程中進行UI操作:
1. Handler的post()方法
2. View的post()方法
3. Activity的runOnUiThread()方法
我們先來看下Handler中的post()方法,代碼如下所示:
[java] view plaincopy
public ?final ?boolean ?post(Runnable?r)??{?? ???return ??sendMessageDelayed(getPostMessage(r),?0 );?? }??
原來這里還是調用了sendMessageDelayed()方法去發送一條消息啊,并且還使用了getPostMessage()方法將Runnable對象轉換成了一條消息,我們來看下這個方法的源碼:
[java] view plaincopy
private ?final ?Message?getPostMessage(Runnable?r)?{??????Message?m?=?Message.obtain();?? ????m.callback?=?r;?? ????return ?m;?? }??
在這個方法中將消息的callback字段的值指定為傳入的Runnable對象。咦?這個callback字段看起來有些眼熟啊,喔!在Handler的dispatchMessage()方法中原來有做一個檢查,如果Message的callback等于null才會去調用handleMessage()方法,否則就調用handleCallback()方法。那我們快來看下handleCallback()方法中的代碼吧:
[java] view plaincopy
private ?final ?void ?handleCallback(Message?message)?{??????message.callback.run();?? }??
也太簡單了!竟然就是直接調用了一開始傳入的Runnable對象的run()方法。因此在子線程中通過Handler的post()方法進行UI操作就可以這么寫:
[java] view plaincopy
public ?class ?MainActivity?extends ?Activity?{???? ????private ?Handler?handler;?? ?? ????@Override ?? ????protected ?void ?onCreate(Bundle?savedInstanceState)?{?? ????????super .onCreate(savedInstanceState);?? ????????setContentView(R.layout.activity_main);?? ????????handler?=?new ?Handler();?? ????????new ?Thread(new ?Runnable()?{?? ????????????@Override ?? ????????????public ?void ?run()?{?? ????????????????handler.post(new ?Runnable()?{?? ????????????????????@Override ?? ????????????????????public ?void ?run()?{?? ?????????????????????????? ????????????????????}?? ????????????????});?? ????????????}?? ????????}).start();?? ????}?? }??
雖然寫法上相差很多,但是原理是完全一樣的,我們在Runnable對象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI。
然后再來看一下View中的post()方法,代碼如下所示:
[java] view plaincopy
public ?boolean ?post(Runnable?action)?{??????Handler?handler;?? ????if ?(mAttachInfo?!=?null )?{?? ????????handler?=?mAttachInfo.mHandler;?? ????}?else ?{?? ????????ViewRoot.getRunQueue().post(action);?? ????????return ?true ;?? ????}?? ????return ?handler.post(action);?? }??
原來就是調用了Handler中的post()方法,我相信已經沒有什么必要再做解釋了。
最后再來看一下Activity中的runOnUiThread()方法,代碼如下所示:
[java] view plaincopy
public ?final ?void ?runOnUiThread(Runnable?action)?{??????if ?(Thread.currentThread()?!=?mUiThread)?{?? ????????mHandler.post(action);?? ????}?else ?{?? ????????action.run();?? ????}?? }??
如果當前的線程不等于UI線程(主線程),就去調用Handler的post()方法,否則就直接調用Runnable對象的run()方法。還有什么會比這更清晰明了的嗎?
通過以上所有源碼的分析,我們已經發現了,不管是使用哪種方法在子線程中更新UI,其實背后的原理都是相同的,必須都要借助異步消息處理的機制來實現,而我們又已經將這個機制的流程完全搞明白了,真是一件一本萬利的事情啊。
第一時間獲得博客更新提醒,以及更多技術信息分享,歡迎關注我的微信公眾號,掃一掃下方二維碼或搜索微信號guolin_blog,即可關注。
總結
以上是生活随笔 為你收集整理的Android异步消息处理机制完全解析,带你从源码的角度彻底理解 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。