1. 程式人生 > >Android之事件匯流排EventBus詳解

Android之事件匯流排EventBus詳解

顧名思義,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);  
        }  
    }  
是不是有很麻煩的感覺?我們再來看一個示例,在開發過程中,我們經常要在子執行緒中做一些耗時操作,然後將結果更新到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();  
        }  
    }  

是不是依然相當麻煩?當然你也可以使用AsyncTask來簡化操作,但AsyncTask的幾個泛型引數讓你的程式碼看起來並不那麼簡潔,因此GitHub上出現了TinyTask、SimpleTask這些開源庫來簡化AsyncTask的使用。而這些,使用AndroidEventBus都可以很好地解決!

下面就讓我們來領悟一下AndroidEventBus的強大魅力吧。

初見AndroidEventBus

使用AndroidEventBus簡單概括只有三個步驟:

  • 將物件註冊到AndroidEventBus中;
  • 使用@Subcriber標註訂閱函式(只能有一個引數);
  • 通過post函式釋出事件。

接下來就是註冊訂閱物件,如程式碼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);  
    }      
    // 程式碼省略  
}      

在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中,我們為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(這是一個執行在非同步執行緒的事件);引數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的功能遠不止於此,下面我們就看看如何進行更高階的操作。


還可以怎麼玩?

退出應用的另類實現

在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中,我們定義一個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;  
  }  
EventHandler的介面定義如程式碼7所示,只需實現handleEvent即可,然後將該實現注入到EventBus即可。
/** 
 * 事件處理介面,處理事件的抽象 
 */  
public interface EventHandler {  
    /** 
     * 處理事件 
     * @param subscription 訂閱物件 
     * @param event 待處理的事件 
     */  
    void handleEvent(Subscription subscription, Object event);  
}  
預設有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);  
            }  
        });  
    }  
}  

然後通過如下程式碼將ThreadPoolEventHandler注入到:

EventBus中:

// 自定義的非同步事件處理器,使用執行緒池  
 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();  
        }  
    }  
}  

最後,當需要下載圖片時,通過post釋出一個引數為String型別、tag為download的事件即可執行downloadImage函式,這個函式將執行線上程池中,我們的簡易ImageLoader就這麼實現了。圖2、圖3分別為圖片下載中和圖片下載完成的頁面。

當然,由於AndroidEventBus的高度定製化,我們還可以通過AndroidEventBus來實現各種各樣的功能,它到底還能怎麼玩,我就不做過多的演示了,開發者可以充分發揮自己的聰明才智和想象力。