Android 中 EventBus 的使用(2):緩衝事件

來源:互聯網
上載者:User

標籤:

在上一篇文章中,我曾提到我所選擇的是Green Robot提供的EventBus(Android平台),而且這並非只是我一個人的選擇。在最近一次查看中,我發現選擇它的人數已經是Otto(由Jake Wharton和其他大神們在Square上所提供的版本)的兩倍之多了。GR的版本顯然比Otto有更多的效能提升,但最令我動心的地方在於它添加了很多新功能。今天我就打算談談其中的一項:通過sticky事件進行事件緩衝。

sticky是什嗎?

sticky事件就是指在EventBus內部被緩衝的那些事件。EventBus為每個類(class)類型儲存了最近一次被發送的事件——sticky。後續被發送過來的相同類型的sticky事件會自動替換之前緩衝的事件。當一個監聽者向EventBus進行註冊時,它可能會去請求這些緩衝事件。這時,所有已緩衝的事件就會立即自動發送給這個監聽者,就象這些事件又重新剛被發送了一次一樣。這就意味著,一個監聽者可以收到在它註冊之前就已經被發送到EventBus中的事件(甚至是在這個監聽者的執行個體被建立出來前,這一點是不是很奇妙)。這一強大功能將有助於我們解決某些固有的問題,如Android上跨Activity和Fragment生命週期傳遞資料這種複雜問題,非同步呼叫等等。

使用sticky
使用sticky事件需要從兩個方面進行:

  • 一、寄件者必須通過調用bus.postSticky(event)將事件進行緩衝。
  • 二、監聽者須調用bus.registerSticky(this)以擷取緩衝的事件。

當調用了bus.registerSticky(this)後,監聽者會立即收到所有已在onEvent處理常式中定義過的那些已緩衝的事件。另外,監聽者也可以根據需要通過bus.getStickyEvent(SomeEvent.class)來擷取這些緩衝事件。

(註:調用postSticky,會像普通的post調用一樣將事件發送給所有當前活動的監聽者,而不是僅限於那些通過registerSticky註冊的。registerSticky僅僅是使緩衝事件在註冊時被重發。)

sticky事件在緩衝中存在的時間並不確定。所以如果你想在某一時刻消除緩衝中的事件好讓它們不再被發送,可以通過bus.removeStickyEvent(event)或bus.removeStickyEvent(SomeEvent.class),以及bus.removeAllStickyEvents()來實現。

令人厭惡的Bundles

我在上一篇文章中曾說過,我並不喜歡Android中的Bundle,而且盡量避免使用它們。我不喜歡被象Serializable或者是Parcelable這類對象所約束,尤其是它們還缺少對型別安全的檢查。我的意思是必竟這是Java,而不是Python或者Javascript什麼的。我希望我的IDE能夠發現並告訴我這樣的錯誤,如一個組件向另外一個組件發送了一個不是它期望的物件類型。

不要誤會,Intent在處理序間通訊時還是很有用的,在這種情況下將攜帶的資料序列化成通用格式是合情合理的。但如果僅僅是為了在使用者旋轉了一下螢幕後,讓程式保持原來的狀態,以科學的名義說,有必要非得用這種方法嗎?沒錯,我說的就是Android提供的處理配置改變的標準模式——在onSaveInstanceState(Bundle bundle)和onRestoreInstanceState(Bundle bundle)中儲存和恢複狀態資料。且不提那些荒唐複雜的Fragment生命週期問題,單單是保持運行狀態的這種處理方式就是我最不喜歡的Android開發特點之一。

Stinky Bundles Sticky Events

程式運行狀態除了儲存到Bundle中,另一種方法是將它們儲存到某些在配置改變時依然生存著的對象中去。GR的EventBus剛好內建了這種緩衝機制可供我們使用。

考慮下面這個響應“Master/Detail流程”的標準情境:

  • 有一個List組件(通常為Fragment)顯示一個摘要列表。
  • 另外一個組件(另一個Fragment)顯示每一項的詳細內容。
  • 點擊某一清單項目可顯示對應的詳細資料。
  • 在豎屏模式中,列表和詳細資料分為兩頁,各佔一屏,每次只能看到一個頁面。
  • 在橫屏模式中,列表在螢幕的左側,詳細資料在右側,當左側的清單項目被選中時,右側的詳細資料也隨之改變。
  • 主Activity中包含一個布局(layout)用於在不同模式下進行切換。

在這個例子中具有挑戰性的是,當使用者在橫豎屏間來回切換時,程式要如何維護當前所選項的狀態。這個狀態的重要性不但在於詳細頁面需要知道該顯示哪條詳細資料,而且列表也需要顯示出當前哪一項被選中了。此外,對於首頁面來說也需要知道當前是否有項目被選中,以便決定在豎屏模式時需要載入哪個頁面,列表或詳細資料。

如你所見,這三個組件都需要同一個狀態資訊(被選中項)。使用傳統方法,這三個組件每一個都需要在各自的onSaveInstanceState方法中將這一狀態儲存進Bundle中,然後再從各自的onResumeInstanceState方法裡把資料取回來。不爽!

然而使用sticky事件,事情就變得簡單多了。為了更好地說明問題,我建立了一個Android樣本工程:https://github.com/wongcain/EventBus-Config-Demo/ 下面所有的範例程式碼都包含在這個工程裡。

首先,建立一個事件類別(ItemSelectedEvent.java)用於傳遞被選中項的位置資訊:

     Java 
123456 public class ItemSelectedEvent {public final int position;public ItemSelectedEvent(int position) {this.position = position;}}

然後在List組件(ItemListFragment.java)的listItemClick方法裡發送一個sticky事件:

      Java 
12345 @Overridepublic void onListItemClick(ListView listView, View itemView, int position, long id) {super.onListItemClick(listView, itemView, position, id);bus.postSticky(new ItemSelectedEvent(position));}

接下來,Detail組件(ItemDetailFragment.java)註冊接收sticky事件,並定義一個ItemSelectedEvent的處理方法。當收到事件時,查詢並顯示被選中項的詳細資料:

     Java 
1234567891011121314151617181920 @Overridepublic void onResume() {super.onResume();bus.registerSticky(this);} @Overridepublic void onPause() {bus.unregister(this);super.onPause();} ... public void onEvent(ItemSelectedEvent event) {Item item = MockDataSource.ITEMS.get(event.position);titleView.setText(item.title);dateView.setText(item.getDateStr());bodyView.setText(item.body);}

最後,在Main組件(MainActivity.java)中將所有內容集合到一起。Activity自身註冊監聽sticky事件,並建立與Detail組件一樣的ItemSelectedEvent處理方法。當收到事件時,根據當前頁面配置(layout)決定將Detail fragment載入哪個合適的容器中。

     Java 
12345678910111213141516171819202122232425 @Overrideprotected void onResume() {super.onResume();bus.registerSticky(this);bus.postSticky(new LayoutEvent(isTwoPane()));} @Overrideprotected void onPause() {bus.unregister(this);super.onPause();} public void onEvent(ItemSelectedEvent event) {if(isTwoPane()){getFragmentManager().beginTransaction().replace(detailContainer.getId(), new ItemDetailFragment()).commit();} else {getFragmentManager().beginTransaction().replace(listContainer.getId(), new ItemDetailFragment()).addToBackStack(ItemDetailFragment.class.getName()).commit();}}

注意,這個activity不僅監聽sticky事件,還發送了另外一個sticky事件用來傳遞當前螢幕模式。這一事件隨後會被List fragment(ItemListFragment.java)收到,並且根據條件對列表進行設定:

     Java 
12345678 public void onEvent(LayoutEvent event) {if(event.isTwoPane){getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);updateSelectedItem(activePosition);} else {getListView().setChoiceMode(ListView.CHOICE_MODE_NONE);}}

另外可以看到,沒有一個組件要去實現onSaveInstanceState(Bundle bundle)以及onRestoreInstanceState(Bundle bundle)方法。取而代之的是它們只需簡單地依賴於在registerSticky(this)時自動發送的緩衝事件。所以,當使用者選擇一個項目並且在查看詳細資料時,以下情況便會在配置改變時自動發生:

  1. 在onPause時,每個組件都會將自身從EventBus登出掉。
  2. Main activity重啟並在它的onResume方法裡註冊監聽sticky事件。
  3. 緩衝的ItemSelectedEvent被發送到Main activity,然後Detail fragment被載入。
  4. Detail fragment的onResume被調用並且接收到ItemSelectedEvent,從而使得被選中項目的詳細資料被顯示出來。
  5. 此外,List fragment的onResume被調用並且收到ItemSelectedEvent和LayoutEvent,然後根據當前布局正確地顯示被選中項目。

希望這篇文章對你能有協助。如之前提到的,所有的範例程式碼都可以在這裡訪問到:https://github.com/wongcain/EventBus-Config-Demo/

下一篇將是有關EventBus系列教程的最後一篇, 我將談一談在EventBus中有關跨越多線程和進程的有關內容。

全能程式員交流QQ群290551701,聚集很多互連網精英,技術總監,架構師,專案經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

Android 中 EventBus 的使用(2):緩衝事件

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.