[譯]Android Flux 框架 RxFlux 介紹

原文

簡介

RxFlux 是一個使用 RxJava1 實現 Flux模式 的輕量級框架,RxFlux 僅僅提供 Flux 模式的一種實現,它需要手動集成,并且需要開發者遵守 Flux 步驟才可以正常工作。

優勢: 架構清晰、步驟簡單、邏輯抽象、容易測試、模塊化、響應式、添加 Hooks 容易。
不足: 線性邏輯、不同的模式,使用前需要學習。

Wiki-1:Getting started

RxFlux 是一個框架,意味著需要做一些準備工作,而不僅僅是作為一個 library。使用 RxFlux 框架最好了解 RxJava 和 Flux。如果你需要更多的知識來理解 Android 中的 Flux 模式,可以查看@lgvalle 創建的例子和說明

記住 Flux 的框架能更好的理解 RxFlux。

Flux architecture

第一步添加 RxFlux 到 build.gradle 文件中:

dependencies {
  compile 'com.hardsoftstudio:rxflux:latest'
}
public void onCreate() {
    super.onCreate();
    rxFlux = RxFlux.init();
}

Wiki-2:Setup Action

創建接口 Actions 包含你的 actions:

public interface Actions {

  String GET_PUBLIC_REPOS = "get_public_repos";
  void getPublicRepositories();

  String GET_USER = "get_user";
  void getUserDetails(String userId);
}

創建一個 RxActionCreator 類將創建 RxAction 根據前面的接口 Actions:

public class GitHubActionCreator extends RxActionCreator implements Actions { 

  public GitHubActionCreator(Dispatcher dispatcher, SubscriptionManager manager) {
    super(dispatcher, manager);
  }

  //實現定義的actions
}

大部分的 actions 是請求一個 API,DB 或你想執行的操作。因此創建 RxAction 時,您必須返回這些請求的結果。推薦創建一個接口 Keys,將幫助我們整理數據。

public interface Keys {
  String PUBLIC_REPOS = "repos";
  String USER = "user";
  String ID = "id";
}

創建第一個 action:

  @Override
  public void getUserDetails(String userId) {
    final RxAction action = newRxAction(GET_USER, Keys.ID, userId);
    ...
  }

方法 newRxAction()幫助你創建一個新的 RxAction。傳入參數 action type 和 key-value。

現在我們有一個 RxAction,我們可以請求 API 或處理一些邏輯來獲取所需的 action 的結果。

  @Override
  public void getUserDetails(String userId) {
    final RxAction action = newRxAction(GET_USER, Keys.ID, userId);
    addRxAction(action, NetworkManager.getApi()
        .getUser(userId)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(user -> {
          action.getData().put(USER, user);
          postRxAction(action);
        }, throwable -> postError(action, throwable)));
  }

這里我們使用 RxJava,我們創建一個新的 Observable 并執行 subscribe。產生的 Subscription 被添加到 SubscriptionManager。

當這個 Subscription 返回一個有效的數據時,我們用 key-value 加入到 RxAction。然后我們使用預定義的方法 postRxAction() 發送 action 到 Dispatcher。

在出錯的情況下,我們將使用預定義的方法 postError() 發送一個 error 到 Dispatcher。

為避免重復的請求,當你添加一個 Subscription 到 SubscriptionManager,這個 Subscription 將保存直到它被 unsubscribed。使用 hasRxAction(RxAction)方法來檢查是否有一個正在進行 Subscription。

  @Override
  public void getUserDetails(String userId) {
    final RxAction action = newRxAction(GET_USER, Keys.ID, userId);
    if (hasRxAction(action)) return; // Return or cancel previous 
    ...
   }

Wiki-3:Setup Store

第一步定義 store,創建接口。

public interface RepositoriesStoreInterface {

  ArrayList<GitHubRepo> getRepositories();

}

創建一個 store 繼承 RxStore。

public class RepositoriesStore extends RxStore implements RepositoriesStoreInterface {
    ...
}

RxStore 是一個抽象類,同 Dispatcher 一起處理 subscription 來接收 actions。為接收這些 actions,我們需要實現 onRxAction()

public class RepositoriesStore extends RxStore implements RepositoriesStoreInterface {

  public RepositoriesStore(Dispatcher Dispatcher) {
    super(Dispatcher);
  }

  @Override
  public void onRxAction(RxAction action) {

  }
}

構造函數需要 Dispatcher 作為參數,以便訂閱它。onRxAction() 方法將得到Dispatcher 發布的所有 actions。使用 switch 語句來過濾這些 actions。

  @Override
  public void onRxAction(RxAction action) {
    switch (action.getType()) {
      case Actions.GET_PUBLIC_REPOS:
        this.gitHubRepos = action.get(Keys.PUBLIC_REPOS);
        break;
      default: // IMPORTANT 不需要處理的action,被忽略
        return;
    }
    postChange(new RxStoreChange(ID, action));
  }

因為 store 將接收所有類型的 actions,而我們只處理那些我們關心的。為了做到這一點,我們檢索 actions 中數據并處理,然后發布一個 RxStoreChange,通知 views。使用方法 postChange() 發布 RxStoreChange。這種方法發布的RxStoreChange 包含將一個給定的 store id (主要用于標識那個 store 發出的)和 RxAction。

最后一步是實現公共接口,提供的我們想要的數據。

  @Override
  public ArrayList<GitHubRepo> getRepositories() {
    return gitHubRepos == null ? new ArrayList<GitHubRepo>() : gitHubRepos;
  }

views 可以在接收到一個 RxStoreChange 時或者任何需要的時候,調用該方法,得到 store 中的數據。

Wiki-4:Setup View

views 負責觸發 actions,注冊 stores 并使用 stores 的數據。

第一步是項目中的每個 activity 必須實現 RxViewDispatch。

getRxStoreListToRegister() 告訴 RxFlux 哪個 store 需要注冊。

  @Override
  public List<RxStore> getRxStoreListToRegister() {
    repositoriesStore = RepositoriesStore.get(SampleApp.get(this).getRxFlux().getDispatcher());
    usersStore = UsersStore.get(SampleApp.get(this).getRxFlux().getDispatcher());
    return Arrays.asList(repositoriesStore, usersStore);
  }

我們如何建立我們的商店。在這個例子中,我們得到的需要注冊的 store 的實例。RxFlux 在 activity 創建成功之后會調用該方法,若 activity 是 RxViewDispatch 的子類,獲取需要注冊的 RxStoreList 并調用 RxStore 中的方法 register(),注冊 store 到 Dispatcher 中。

Note: 為了安全,在多次調用注冊時,我們只注冊一次。

onRxStoreChanged() 在每次 store 發送一個 RxStoreChange 的時候將被調用。

  @Override
  public void onRxStoreChanged(RxStoreChange change) {
    switch (change.getStoreId()) {
      case RepositoriesStore.ID:
        switch (change.getRxAction().getType()) {
          case Actions.GET_PUBLIC_REPOS:
            adapter.setRepos(repositoriesStore.getRepositories());
            break;
        }
        break;
    }
  }

RxStoreChange 有一個 store 的標識和一個 RxAction。通過這兩個,views 可以執行期望的邏輯。

onRxError() 在 RxError 被 post 到 Dispatcher 時調用。

  @Override
  public void onRxError(RxError error) {
    setLoadingFrame(false);
    Throwable throwable = error.getThrowable();
    if (throwable != null) {
      Snackbar.make(coordinatorLayout, "An error ocurred", Snackbar.LENGTH_INDEFINITE)
          .setAction("Retry",
              v -> SampleApp.get(this).getGitHubActionCreator().retry(error.getAction()))
          .show();
    } else {
      Toast.makeText(this, "Unknown error", Toast.LENGTH_LONG).show();
    }
  }

這種方法允許我們作出反應,當我們得到一個錯誤時。注意 RxError 包含期望的 RxAction。因此,我們可以使用它來重試或顯示適當的信息。

RxViewDispatcher 的最后兩個方法是用來注冊其他非 activity 的 view(fragments,customViews等)。


  @Override
  public void onRxViewRegistered() {
    // If there is any fragment that needs to register store changes we can do it here
  }

  @Override
  public void onRxViewUnRegistered() {
    // If there is any fragment that has registered for store changes we can unregister now
  }

高級選項

RxFlux 將管理應用程序生命周期,在正確的時候注冊和注銷 views,避免內存泄漏。如果應用程序被destroyed,還將負責清理任何訂閱。

有關更多信息,請查看 RxFlux.class (主類,負責連接每個部分,基于 Android 的生命周期)。

Flow

  1. RxFlux 會在 activity 創建時,注冊我們所需要的 store,調用
    getRxStoreListToRegister()
  2. 在 activity resume 時,RxFlux 將注冊這個 activity 到 Dispatcher 中。
  3. 在 activity pause 時,RxFlux 從 Dispatcher 中解除這個 activity 的注冊。
  4. 當最后一個 activity 被 destroy 時,RxFlux 將調用關閉。

為什么在 onResume() 注冊和在 onPause() 注銷?

根據 activity 的生命周期來處理注冊和解除注冊,這是正確的方法,可以避免當 view 不在前臺的時候,獲取 RxStoreChange 并響應觸發 UI 變化。
 
一個良好的實踐是在 onResume() 期間,檢查 store 的狀態并更新 UI。

公共方法

  public static RxFlux init(Application application) {
    if (instance != null) throw new IllegalStateException("Init was already called");
    return instance = new RxFlux(application);
  }

初始化 RxFlux 框架,必須在 Application 創建時調用此方法,并只有一次。

  public static void shutdown() {
    if (instance == null) return;
    instance.subscriptionManager.clear();
    instance.Dispatcher.unregisterAll();
  }

調用這個方法停止應用程序,清理所有的訂閱和注冊。

  public RxBus getRxBus() {
    return rxBus;
  }

RxBus 的實例,如果您想在您的應用程序中重用作為默認的 bus system。

  public Dispatcher getDispatcher() {
    return Dispatcher;
  }

Dispatcher 的實例,負責處理訂閱,傳送 bus event 到正確的位置。在創建 RxActionCreator 時,使用這個實例。

  public SubscriptionManager getSubscriptionManager() {
    return subscriptionManager;
  }

SubscriptionManager 的實例,以便你想要重用的別的東西。在創建 RxActionCreator 時,使用這個實例。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容