EventBus原始碼詳解(一):基本使用
寫在前面
對於Android程式設計師來說,相信大家都聽過EventBus的大名。EventBus是一個Android平臺上基於事件釋出和訂閱的輕量級框架,可以對釋出者和訂閱者解耦,並簡化Android的事件傳遞。正如官方介紹其優勢:
- 簡化了元件之間的通訊
- 解耦事件的傳送者和接收者
- 在Activity、Fragment和後臺執行緒表現良好
- 避免複雜和易出錯的依賴性和生命週期問題
- 使你的程式碼更加簡潔
- 快速和輕量
接下來,讓我們一起從EventBus的使用到原始碼解讀,全方位地解讀這個輕量但功能強大的開源框架。
正文
本文先從EventBus的簡單使用開始,先介紹EventBus
配置和簡單使用
在app下的build.gradle配置:
compile 'org.greenrobot:eventbus:3.0.0'
是的,就新增這句就可以了,非常簡單。當然還可以配置生成索引,這部分在進階使用文章裡介紹。
定義一個事件類:
public static class MessageEvent { /* Additional fields if needed */ }
在訂閱者類中定義一個用註解“@Subscribe”標記並引數為事件類的方法:
@Subscribe
public void onMessageEvent(MessageEvent event) {/* Do something */};
然後在訂閱者類中註冊和登出,在Android上,通常在Activity或Fragment的生命週期裡處理:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this); // 註冊
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this ); // 登出,當訂閱者不再訂閱事件時,必須登出,否則可能發生記憶體洩漏
}
最後,傳送事件,每個訂閱者註冊的事件都將收到訊息(會呼叫“@Subscribe”標記的方法):
EventBus.getDefault().post(new MessageEvent());
舉例
現在來舉個栗子來展示EventBus的魅力!想想一個閱讀App看書的場景:左邊是書籍選擇列表,右邊是書籍的內容。這時我們大多數情況都是用Fragment來處理,也即左右兩邊都是Fragment,這就涉及兩個Fragment的通訊問題。我們通常的處理方式是在Fragment新增介面回撥,然後Activitty作為兩個Fragment的通訊橋樑。
或許有人說直接在Activity獲取Fragment裡的控制元件,互動更加簡單。這樣雖然簡單,但Activity的職責變得很重,而Fragment的作用之一就是分擔Activity的職責,所以這是不可取的。
好,我們先來看不用EventBus時用回撥的方式來實現Fragment的通訊的例子。
首先先定義兩個Fragment,左邊的Fragment1,佈局只有一個ListView(佈局程式碼不貼,後面有原始碼),在Fragment1裡定義一個介面,當點選ListView的item時,就把所點選的書籍回撥給外部:
public class Fragment1 extends Fragment {
private final static String[] BOOKS = new String[] {
"第一行程式碼",
"Java程式設計思想",
"Android開發藝術探索",
"Android原始碼設計模式",
"演算法",
"研磨設計模式"
};
ListView listView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container, false);
listView = (ListView) view.findViewById(R.id.lv_catalogue);
ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, BOOKS);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 回撥
if (listener != null) {
listener.onCLickBook(BOOKS[position]);
}
}
});
return view;
}
private OnClickBookListener listener;
public void setOnClickBookListener(OnClickBookListener listener) {
this.listener = listener;
}
/**
* 點選書籍監聽介面
*/
public interface OnClickBookListener {
void onCLickBook(String book);
}
}
右邊的Fragment2就更加簡單了,佈局只有一個TextView來展示書籍的內容:
public class Fragment2 extends Fragment {
TextView textView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment2, container, false);
textView = (TextView) view.findViewById(R.id.tv_content);
return view;
}
/**
* 需要暴露介面給外部來互動
*/
public void readBook(String book) {
textView.setText(book);
}
}
最後,MainActivity作為兩個Fragment的通訊橋樑,來實現兩個Fragment的通訊:
public class MainActivity extends AppCompatActivity {
Fragment1 fragment1;
Fragment2 fragment2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragment1 = (Fragment1) getSupportFragmentManager().findFragmentById(R.id.fragment1);
// 設定監聽
fragment1.setOnClickBookListener(new Fragment1.OnClickBookListener() {
@Override
public void onCLickBook(String book) {
// 更新fragment2的閱讀書籍
fragment2.readBook(String.format("我正在閱讀%s", book));
}
});
fragment2 = (Fragment2) getSupportFragmentManager().findFragmentById(R.id.fragment2);
}
}
從上面程式碼可以看到,兩個Fragment的通訊也非常簡單,而Activity的職責也明顯降低,只需作為橋樑,那麼我們還有必要用EventBus嗎?答案是有必要的!這只是一個例子,而實際開發中兩個Fragment通訊互動幾乎不可能這麼簡單,往往是縱橫交錯的,這時難道在Fragment上定義多個監聽介面,然後在Activity上處理這些邏輯?這就又導致了Activity職責過大,而耦合度也顯著增加了。所以,使用回撥的通訊方式似乎適合互動較少的兩個Fragment,但一旦互動過多,就無能為力了。下面再來看看EventBus怎麼實現Fragment的通訊。
首先,先定義一個閱讀書籍的事件類(注意,最好用public修飾,否則有可能在生成索引時失敗,這些內容在後續文章會講解,現在先記著):
public class ReadBookEvent {
private String book;
ReadBookEvent(String book) {
this.book = book;
}
public String getBook() {
return book;
}
}
顯然,因為Fragment2是監聽Fragment1的點選事件作出更新響應,所以,這裡Fragment2是訂閱者(注意:與事件類一樣,訂閱者最好宣告為public,否則有可能在生成索引失敗):
public class Fragment2 extends Fragment {
TextView textView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment2, container, false);
textView = (TextView) view.findViewById(R.id.tv_content);
return view;
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this); // 註冊訂閱者
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this); 登出訂閱者
}
/**
* 監聽閱讀事件
*/
@Subscribe
public void onReadBook(ReadBookEvent event) {
textView.setText(String.format("我正在閱讀%s", event.getBook()));
}
}
而Fragment1不用定義回撥介面了,只需在點選書籍時傳送事件:
public class Fragment1 extends Fragment {
private final static String[] BOOKS = new String[] {
"第一行程式碼",
"Java程式設計思想",
"Android開發藝術探索",
"Android原始碼設計模式",
"演算法",
"研磨設計模式"
};
ListView listView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, container, false);
listView = (ListView) view.findViewById(R.id.lv_catalogue);
ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, BOOKS);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
EventBus.getDefault().post(new ReadBookEvent(BOOKS[position])); // 傳送事件
}
});
return view;
}
}
另外在MainActivity裡,不用再處理Fragment的互動了,甚至不用建立Fragment的例項,直接解耦:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
從上面程式碼可以看到,EventBus很好地使兩個Fragment解耦,並優雅地實現了兩者的通訊,同時也減少了Fragment和Activity的耦合,真正的一舉多得!再想想兩個Activity之間的通訊,我們通常使用Intent作為載體,來傳遞資料,使用EventBus就可以拋開Intent了,直接實現Activity間的通訊!這裡就不再舉例了,相信聰明的你能舉一反三。
至此,相信大家對EventBus有了基礎的認知了。
寫在最後
EventBus所帶來的便利和簡潔有沒有使你震驚?!別滿足的太早,EventBus如此受人待見,當然有她的厲害之處。後續文章將會介紹使用編譯期處理生成索引來提升事件傳送的效率,還有EventBus的高階配置,敬請期待!