Android事件總線還能怎麼玩?
顧名思義,AndroidEventBus ( github鏈接 ,關於我為什麼要寫這個庫請參考《AndroidEventBus ( 事件總線 ) 的設計與實現》)是一個Android平臺的事件總線框架,它簡化瞭Activity、Fragment、Service等組件之間的交互,很大程度上降低瞭它們之間的耦合,使我們的代碼更加簡潔,耦合性更低,提升瞭我們的代碼質量。但它能做的卻不僅限於這些。經過定制,它能完成很多有意思的功能,那麼究竟該怎麼做呢?就讓我們一起往下看吧。
不堪回首的痛
首先,讓我們先來看看這麼一個場景:你是否在開發的過程中遇到過從Activity-A跳轉到Activity-B,然後需要在Activity-B處理完某些工作之後回調Activity-A中的某個函數,但Activity又不能手動創建對象來設置一個Listener的情況?或者遇到在某個Service中更新Activity或Fragment中的界面等組件之間的交互問題……
一經思考,你會發現Android中的Activity、Fragment、Service之間的交互是比較麻煩的,可能我們第一想到的是使用廣播接收器來在它們之間進行交互。如上文所說,在Activity-B中發一個廣播,在Activity-A中註冊一個廣播接收器來接收該廣播。但使用廣播接收器稍顯麻煩,如果你要將一個實體類當作數據在組件之間傳遞,那麼該實體類還得實現序列化接口,這個成本實在有點高!如代碼1所示。
class ActivityA extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ActivityA中註冊廣播接收器
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
User person = intent.getParcelableExtra("user") ;
}
}, new IntentFilter("my_action")) ;
}
// ......
}
// ActivityB中發佈廣播
class ActivityB extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 發佈廣播
Intent intent = new Intent("my_ action");
intent.putExtra("user", new User("mr.simple")) ;
sendBroadcast(intent);
}
// ......
}
// 實體類需要實現序列化
class User implements Parcelable {
String name ;
public User(String aName) {
name = aName ;
}
// 代碼省略
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
}
代碼1
是不是有很麻煩的感覺?我們再來看一個示例,在開發過程中,我們經常要在子線程中做一些耗時操作,然後將結果更新到UI線程,除瞭AsyncTask之外,Thread加Handler是我們經常用的手段。如代碼2所示。
class MyActivity extends Activity {
Handler mHandler = new Handler () {
public void handleMessage(android.os.Message msg) {
if ( msg.what == 1 ) {
User user = (User)msg.obj ;
// do sth
}
};
} ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// code ......
new Thread(
new Runnable() {
public void run() {
// do sth
User newUser = new User("simple") ;
Message msg = mHandler.obtainMessage() ;
msg.what = 1 ;
msg.obj = newUser ;
mHandler.sendMessage(msg) ;
}
}).start();
}
}
代碼2
是不是依然相當麻煩?當然你也可以使用AsyncTask來簡化操作,但AsyncTask的幾個泛型參數讓你的代碼看起來並不那麼簡潔,因此GitHub上出現瞭TinyTask、SimpleTask這些開源庫來簡化AsyncTask的使用。而這些,使用AndroidEventBus都可以很好地解決!
下面就讓我們來領悟一下AndroidEventBus的強大魅力吧。
初見AndroidEventBus
使用AndroidEventBus簡單概括隻有三個步驟:
將對象註冊到AndroidEventBus中; 使用@Subcriber標註訂閱函數(隻能有一個參數); 通過post函數發佈事件。
對應的簡單序列圖如下 :
Created with Rapha?l 2.1.2EventBusEventBus訂閱對象訂閱對象註冊發佈事件查找訂閱對象訂閱方法執行在具體的線程模型執行訂閱方法
註冊訂閱對象
註冊訂閱對象,如代碼3所示。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 將對象註冊到事件總線中, ****** 註意要在onDestory中進行註銷 ****
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// ****** 不要忘瞭進行註銷 ****
EventBus.getDefault().unregister(this);
}
// 代碼省略
}
代碼3
在onCreate中註冊之後,MainActivity就可以添加訂閱函數來接收消息瞭。需要註意的是在onDestory中需要將MainActivity從事件總線中註銷。通過AndroidEventBus你可以去除Activity、Fragment、Service等組件的回調,減少瞭耦合,簡化瞭代碼。
事件訂閱函數
事件訂閱需要使用@Subscriber註解進行標識,且訂閱函數的參數必須為一個。事件總線憑借參數類型和@Subscriber註解的tag值來標識訂閱函數的唯一性。當用戶發佈事件時,總線庫會根據事件類型和tag來查找符合要求的訂閱函數,並且將這些訂閱函數執行在對應的線程中。我們先來看看代碼4的訂閱函數示例。
public class MainActivity extends Activity {
// 代碼省略
@Subcriber(tag = "csuicide")
private void csuicideMyself(String msg) {
// do sth
finish();
}
@Subcriber(mode = ThreadMode.MAIN)
private void toastMsgFromEvent(String msg) {
// do sth
}
@Subcriber(tag = "async", mode = ThreadMode.ASYNC)
private void executeAsync(final String msg) {
// do sth
}
// 代碼省略
}
代碼4
在代碼4中,我們為MainActivity添加瞭以下三個訂閱函數:
csuicideMyself:該訂閱函數執行在主線程,接收事件的類型為String,tag為csuicide。當用戶發佈一個事件類型為String,且tag為csuicide的事件時將會觸發該方法。 toastMsgFromEvent:該訂閱函數也是執行在主線程,事件類型為String,且tag為默認。當用戶發佈一個事件類型為String,且tag為默認的事件時將會觸發該方法。 executeAsync:該訂閱函數也是執行在一個異步線程,事件類型為String,且tag為async。當用戶發佈一個事件類型為String,且tag為async的事件時將會觸發該方法。
從上述的描述中我們可以知道,事件接收函數主要有兩個約束:事件類型和tag(類似於Intent中的Action)。添加tag是因為在事件類型一樣時,如果投遞一個消息,那麼單純以事件類型(例如String)作為投遞依據,那麼多個參數為String的訂閱函數將會被觸發,這極大地降低瞭靈活性。
發佈事件
// 參數 1 為事件類型,無 tag
EventBus.getDefault().post("這是一個執行在異步線程的事件");
// 參數 1 為事件類型,參數 2 為 tag,tag 的類型為 String,類似 Intent 的 Action
EventBus.getDefault().post("這是一個執行在異步線程的事件", "async");
發佈事件時可以構造任意類型的事件,如果沒有tag則該參數可以省略。發佈事件後,AndroidEventBus會根據事件類型和tag到已註冊的訂閱對象中查找符合要求的訂閱函數,例如投遞的第二個事件類型為String、tag為async,那麼在MainActivity中符合要求的訂閱函數就是:
@Subcriber(tag = "async", mode = ThreadMode.ASYNC)
private void executeAsync(final String msg) {
// do sth
}
AndroidEventBus的ThreadMode
在上述代碼中有一段代碼是這樣的:
@Subcriber(mode = ThreadMode.MAIN)
private void toastMsgFromEvent(String msg) {
}
這個mode可是大有來頭,它指定這個事件接收函數執行在哪個線程中。具體有如下三個選項:
ThreadMode.MAIN,事件接收函數執行在UI線程; ThreadMode.POST,事件在哪個線程發佈,接收函數就執行在哪個線程; ThreadMode.ASYNC,事件執行在一個獨立的異步線程中。
圖1中,事件接收函數就執行在異步線程。通過這幾個線程模型,我們就可以定制接收函數的執行線程。這樣我們就可以使用AndroidEventBus做很多事瞭。比如發佈一個事件,在這個事件接收函數中進行耗時操作;或下載圖片、進行HTTP請求、I/O操作等,以及替換Thread、AsyncTask等組件。不過,AndroidEventBus的功能遠不止於此,下面我們就看看如何進行更高端的操作。
圖1 接收函數執行在異步線程中
還可以怎麼玩?
退出應用的另類實現
在Android應用開發中,有些情況下我們需要可以直接退出程序。但問題是,回退棧中含有其他的Activity存在,直接使用返回鍵並不能退出應用。此時我們常見的做法是再自定義一個Application子類,在子類中維護一個Activity的列表,然後在進入Activity時,將Activity添加到列表中,在Activity銷毀之前將自己從Application子類的列表中移除。在需要退出應用時遍歷Application子類的Activity列表,然後調用每個Activity的finish函數。那我們看看AndroidEventBus怎麼實現這個功能。如代碼5所示。
public class CsuicideActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 將對象註冊到事件總線中, ****** 註意要在onDestory中進行註銷 ****
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// ****** 不要忘瞭進行註銷 ****
EventBus.getDefault().unregister(this);
}
@Subcriber(tag = "csuicide")
private void csuicideMyself(String msg) {
finish();
}
}
代碼5
代碼5中,我們定義一個CsuicideActivity在onCreate中註冊該Activity對象,在onDestroy中註銷,還添加瞭一個csuicideMyself的訂閱函數。所有的Activity類可以繼承自CsuicideActivity。當需要退出應用時,直接發佈一個類型為String、tag為csuicide的事件即可。這樣所有的Activity就會觸發csuicideMyself,而該函數中又調用瞭finish方法,因此所有的Activity都將退出,通過這種方式就完成瞭應用退出。
自定義事件處理器 ( EventHandler )
AndroidEventBus在設計之初就考慮到瞭可擴展性,主要可擴展的地方就是訂閱函數的搜索策略,具體可以調用EventBus.getDefualt().setMatchPolicy(MatchPolicy policy)來實現策略替換。另一個比較重要的擴展就是事件處理器EventHandler,用戶可以通過setter函數來設置三個事件處理器。如代碼6所示。
/**
* 設置執行在UI線程的事件處理器
* @param handler UI線程事件處理器
*/
public void setUIThreadEventHandler(EventHandler handler) {
mDispatcher.mUIThreadEventHandler = handler;
}
/**
* 設置執行在post線程的事件處理器
* @param handler 事件在哪個線程投遞,事件就執行在哪個線程的事件處理器
*/
public void setPostThreadHandler(EventHandler handler) {
mDispatcher.mPostThreadHandler = handler;
}
/**
* 設置執行在異步線程的事件處理器
* @param handler 異步線程事件處理器
*/
public void setAsyncEventHandler(EventHandler handler) {
mDispatcher.mAsyncEventHandler = handler;
}
代碼6
EventHandler的接口定義如代碼7所示,隻需實現handleEvent即可,然後將該實現註入到EventBus即可。
/**
* 事件處理接口,處理事件的抽象
*/
public interface EventHandler {
/**
* 處理事件
* @param subscription 訂閱對象
* @param event 待處理的事件
*/
void handleEvent(Subscription subscription, Object event);
}
代碼7
默認有DefaultEventHandler、UIThreadEventHandler、AsyncEventHandler三個實現:
DefaultEventHandler:事件在哪個線程發佈,就將事件接收函數執行在哪個線程; UIThreadEventHandler:將事件接收函數執行在UI線程; AsyncEventHandler:將事件接收函數執行在異步線程。
下面我們以自定義異步事件處理器,也就是AsyncEventHandler,通過實現EventHandler接口,將事件處理函數執行在一個線程池中,從而實現圖片下載的功能。如代碼8所示。
public class ThreadPoolHandler implements EventHandler {
ExecutorService mExecutorService = Executors.newFixedThreadPool(3);
EventHandler mHandler = new DefaultEventHandler();
@Override
public void handleEvent(final Subscription subscription, final Object event) {
mExecutorService.submit(new Runnable() {
@Override
public void run() {
mHandler.handleEvent(subscription, event);
}
});
}
}
代碼8
然後通過如下代碼將ThreadPoolEventHandler註入到AndroidEventBus中:
// 自定義的異步事件處理器,使用線程池
EventBus.getDefault().setAsyncEventHandler(new ThreadPoolHandler());
再在訂閱對象中添加代碼9所示的訂閱方法 :
@Subcriber(tag = "download", mode = ThreadMode.ASYNC)
private void downloadImage(final String imageUrl) {
HttpURLConnection urlConnection = null;
try {
final URL url = new URL(imageUrl);
urlConnection = (HttpURLConnection) url.openConnection();
final Bitmap bmp = BitmapFactory.decodeStream(urlConnection.getInputStream());
// 將Bitmap投遞給ImageView之類的工作
} catch (IOException e) {
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
代碼9
最後,當需要下載圖片時,通過post發佈一個參數為String類型、tag為download的事件即可執行downloadImage函數,這個函數將執行在線程池中,我們的簡易ImageLoader就這麼實現瞭。
![]() |
![]() |
---|---|
圖2 圖片下載中 | 圖3 圖片下載完成 |
當然,由於AndroidEventBus的高度定制化,我們還可以通過AndroidEventBus來實現各種各樣的功能,它到底還能怎麼玩,我就不做過多的演示瞭,開發者可以充分發揮自己的聰明才智和想象力。