在這一系列教程的最後一篇中,我想談談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中。我們可以按下面的方式來實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
SomeActivity.java: ... @Inject EventBus bus; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { eventBus.post(new DoOperationEvent()); } }); } @Override protected void onResume() { super.onResume(); bus.register(this); } @Override protected 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; ... @Override protected void onCreate() { super.onCreate(); bus.register(this); } @Override protected void onDestroy() { bus.unregister(this); super.onDestroy(); } public void onEventAsync(DoOperationEvent event){ // DO SOMETHING LONG-RUNNING HERE eventBus.post(new OperationCompleteEvent()); } ... |
儘管這個例子比較簡單,但它卻非常簡明扼要地說明了問題。這裡即不需要實現監聽介面,也不會出現類似生命週期之類的問題(由於activity只能在它存在的時候才能接收到OperationCompleteEvent事件)。除此之外,如果發生了配置改變(旋轉螢幕)或其他什麼原因導致activity在兩次事件發生之間被銷燬並重建,最終仍可以接收到OperationCompleteEvent事件。
此外,我們也可以容易地想到一些其它用法。比如,如果需要將更新進度發出去,你只需另外實現一個封裝了進度值的事件類,然後將其傳送出去即可。或者,如果你想讓其它一些事件(不管是相同還是不同型別)不被並行處理(同步執行),你可以選擇使用onEventBackgroundThread。
依賴Bus
例項化EventBus最簡單的方法就是通過EventBus.getDefault()。然而,在EventBusBuilder類(通過EventBus.builder()獲得)中還包含了另外一些有用的配置方法。特別是在本文中提到過的使用你自己的ExecutorService。預設情況下EventBus通過Executors.newCachedThreadPool()建立自己的ExecutorService,在大多數情況下都已滿足你的需要。然而,有時你可能仍然想要顯示地控制EventBus所使用的執行緒數量,這種情況下你就可以象下面這樣初始化EventBus:
1 |
EventBus.builder().executorService(Executors.newFixedTheadPool(NUM_THREADS)).installDefaultEventBus(); |
在EventBusBuilder中另外一些可供配置的是一些和異常處理的有關的控制,以及一個是否允許事件類被繼承的控制開關。這些內容超出了本文所討論的範圍,但我還是建議你仔細去研究一番。GR可能並沒有把這些內容都寫在文件裡,但如果你讀一讀EventBusBuilder和EventBus的原始碼,相信你會很容易理解它們的。