標籤:
在這一系列教程的最後一篇中,我想談談GR的EventBus,在處理多線程非同步任務時是多麼簡單而有效。
AsyncTask, Loader和Executor…… 拜託!
Android中有很多種執行非同步作業的方法(指平行於UI線程的)。AsyncTask對於使用者來說是最簡單的一種機制,並且只需要少量的設定代碼即可。然而,它的使用是有局限的,正如Android官方文檔中所描述的:
AsyncTask被設計成為一個工具類,在它內部包含了Thread和Handler,但它本身並不是通用線程架構的一部分。AsyncTask應該儘可能地被用在執行一些較短的操作中(最多幾秒)。如果你需要線上程中執行較長時間的任務,那麼建議你直接使用java.util.concurrent包中提供的各種API,如Executor、 ThreadPoolExecutor以及FutureTask。
不過即便是執行短時間的操作也會帶來一些問題,特別是在與Activity/Fragment生命週期有關的地方。由於AsyncTask會持續地運行下去(即使啟動它們的Activity/Fragment已經被銷毀了)。這樣,一旦你在onPostExecute方法中試圖對UI進行更新,那麼最終將導致拋出一個IllegalStateException異常。
Android 3.0中引入了Loader API用來解決Activity/Fragment生命週期的問題(它們的確很有效)。Loader API被設計成向Activity/Fragment中以非同步方式載入資料。儘管載入資料是一種非常常見的非同步作業,但並非唯一一種需要從UI線程中分開的操作。Loader還需要在Activity/Fragment中實現另外一個監聽介面。儘管這麼做沒有錯,但我個人並不喜歡這種模式(我的意思是最終你的代碼中會包含許多的回呼函數,導致代碼的可讀性變得很差)。最後,Activity和Fragment也並非唯一需要對非同步作業分線程的地方。例如如果在Service裡,你就不能訪問LoaderManager,所以最終你還是得使用AsyncTask或者java.util.concurrent。
java.util.concurrent包很不錯,我在Android和非Android項目中都可以使用。不過使用時需要對其進行多一點兒配置和管理,不象AsyncTask那麼簡單。你需要對ExecutorService進行初始化,管理和監視它的生命週期,並且可能需要跟一些Future對象打交道。
只要使用恰當,AsyncTask、 Loader和Executor都是非常有效。但在複雜應用中,需要為每個任務選擇合適的工具,最終你可能三種都會用到。這樣你就得維護三種不同的處理並發的架構代碼。
Green Robot來幫忙了!
GR的EventBus中內建了一個非常棒的並發處理機制。在監聽類中,你可以實現4種不同類型的處理方法。當一個匹配事件被發送過來時,EventBus會根據不同的並行存取模型將事件發送到相應的處理方法中:
- onEvent(T event):運行在和被發送事件相同的線程中。
- onEventMainThread(T event):運行在主(UI)線程中,不管事件從哪個線程中被發送過來。
- onEventAsync(T event):運行在單獨的線程中,即非UI線程,也非發送事件的線程。
- onEventBackgroundThread(T event):如果發送事件的線程不是UI線程,則運行在該線程中。如果發送事件的是UI線程,則它運行在由EventBus維護的一個單獨的線程中。多個事件會同步地被這個單獨的後台線程所處理。
這些方法功能強大而且使用簡單。例如有一個比較耗時的操作(可能是網路調用,大量資料處理等),這一操作需要由UI上的行為來觸發,並且當操作執行完畢後還需對UI進行更新。在這個例子中,UI行為即按鈕點擊,按鈕在activity中,耗時操作在service中。我們可以按下面的方式來實現:
Java
| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 |
SomeActivity.java: ...@Inject EventBus bus;...@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);...button.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {eventBus.post(new DoOperationEvent());}});} @Overrideprotected void onResume() {super.onResume();bus.register(this);} @Overrideprotected void onPause() {bus.unregister(this);super.onPause();} public void onEventMainThread(OperationCompleteEvent event) {Toast.makeText(this, "Operation complete!", Toast.LENGTH_SHORT).show();}... SomeService.java: ...@Inject EventBus bus;...@Overrideprotected void onCreate() {super.onCreate();bus.register(this);} @Overrideprotected void onDestroy() {bus.unregister(this);super.onDestroy();} public void onEventAsync(DoOperationEvent event){// DO SOMETHING LONG-RUNNING HEREeventBus.post(new OperationCompleteEvent());} ... |
儘管這個例子比較簡單,但它卻非常簡明扼要地說明了問題。這裡即不需要實現監聽介面,也不會出現類似生命週期之類的問題(由於activity只能在它存在的時候才能接收到OperationCompleteEvent事件)。除此之外,如果發生了配置改變(旋轉螢幕)或其他什麼原因導致activity在兩次事件發生之間被銷毀並重建,最終仍可以接收到OperationCompleteEvent事件。
此外,我們也可以容易地想到一些其它用法。比如,如果需要將更新進度發出去,你只需另外實現一個封裝了進度值的事件類別,然後將其發送出去即可。或者,如果你想讓其它一些事件(不管是相同還是不同類型)不被平行處理(同步執行),你可以選擇使用onEventBackgroundThread。
依賴Bus
執行個體化EventBus最簡單的方法就是通過EventBus.getDefault()。然而,在EventBusBuilder類(通過EventBus.builder()獲得)中還包含了另外一些有用的配置方法。特別是在本文中提到過的使用你自己的ExecutorService。預設情況下EventBus通過Executors.newCachedThreadPool()建立自己的ExecutorService,在大多數情況下都已滿足你的需要。然而,有時你可能仍然想要顯示地控制EventBus所使用的線程數量,這種情況下你就可以象下面這樣初始化EventBus:
Java
| 1 |
EventBus.builder().executorService(Executors.newFixedTheadPool(NUM_THREADS)).installDefaultEventBus(); |
在EventBusBuilder中另外一些可供配置的是一些和異常處理的有關的控制,以及一個是否允許事件類別被繼承的控制開關。這些內容超出了本文所討論的範圍,但我還是建議你仔細去研究一番。GR可能並沒有把這些內容都寫在文檔裡,但如果你讀一讀EventBusBuilder和EventBus的原始碼,相信你會很容易理解它們的。
全能程式員交流QQ群290551701,聚集很多互連網精英,技術總監,架構師,專案經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!
Android 中 EventBus 的使用(3):多線程事件處理