[譯]Android Flux 框架 RxFlux 介紹

原文

簡(jiǎn)介

RxFlux 是一個(gè)使用 RxJava1 實(shí)現(xiàn) Flux模式 的輕量級(jí)框架批什,RxFlux 僅僅提供 Flux 模式的一種實(shí)現(xiàn),它需要手動(dòng)集成遮咖,并且需要開發(fā)者遵守 Flux 步驟才可以正常工作。

優(yōu)勢(shì): 架構(gòu)清晰懊烤、步驟簡(jiǎn)單、邏輯抽象宽堆、容易測(cè)試腌紧、模塊化、響應(yīng)式畜隶、添加 Hooks 容易寄啼。
不足: 線性邏輯逮光、不同的模式,使用前需要學(xué)習(xí)墩划。

Wiki-1:Getting started

RxFlux 是一個(gè)框架,意味著需要做一些準(zhǔn)備工作嗡综,而不僅僅是作為一個(gè) library乙帮。使用 RxFlux 框架最好了解 RxJava 和 Flux。如果你需要更多的知識(shí)來理解 Android 中的 Flux 模式极景,可以查看@lgvalle 創(chuàng)建的例子和說明察净。

記住 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

創(chuàng)建接口 Actions 包含你的 actions:

public interface Actions {

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

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

創(chuàng)建一個(gè) RxActionCreator 類將創(chuàng)建 RxAction 根據(jù)前面的接口 Actions:

public class GitHubActionCreator extends RxActionCreator implements Actions { 

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

  //實(shí)現(xiàn)定義的actions
}

大部分的 actions 是請(qǐng)求一個(gè) API盼樟,DB 或你想執(zhí)行的操作氢卡。因此創(chuàng)建 RxAction 時(shí),您必須返回這些請(qǐng)求的結(jié)果晨缴。推薦創(chuàng)建一個(gè)接口 Keys译秦,將幫助我們整理數(shù)據(jù)。

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

創(chuàng)建第一個(gè) action:

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

方法 newRxAction()幫助你創(chuàng)建一個(gè)新的 RxAction击碗。傳入?yún)?shù) action type 和 key-value筑悴。

現(xiàn)在我們有一個(gè) RxAction,我們可以請(qǐng)求 API 或處理一些邏輯來獲取所需的 action 的結(jié)果稍途。

  @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阁吝,我們創(chuàng)建一個(gè)新的 Observable 并執(zhí)行 subscribe。產(chǎn)生的 Subscription 被添加到 SubscriptionManager械拍。

當(dāng)這個(gè) Subscription 返回一個(gè)有效的數(shù)據(jù)時(shí)突勇,我們用 key-value 加入到 RxAction。然后我們使用預(yù)定義的方法 postRxAction() 發(fā)送 action 到 Dispatcher坷虑。

在出錯(cuò)的情況下甲馋,我們將使用預(yù)定義的方法 postError() 發(fā)送一個(gè) error 到 Dispatcher。

為避免重復(fù)的請(qǐng)求猖吴,當(dāng)你添加一個(gè) Subscription 到 SubscriptionManager摔刁,這個(gè) Subscription 將保存直到它被 unsubscribed。使用 hasRxAction(RxAction)方法來檢查是否有一個(gè)正在進(jìn)行 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共屈,創(chuàng)建接口。

public interface RepositoriesStoreInterface {

  ArrayList<GitHubRepo> getRepositories();

}

創(chuàng)建一個(gè) store 繼承 RxStore党窜。

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

RxStore 是一個(gè)抽象類拗引,同 Dispatcher 一起處理 subscription 來接收 actions。為接收這些 actions幌衣,我們需要實(shí)現(xiàn) onRxAction()矾削。

public class RepositoriesStore extends RxStore implements RepositoriesStoreInterface {

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

  @Override
  public void onRxAction(RxAction action) {

  }
}

構(gòu)造函數(shù)需要 Dispatcher 作為參數(shù)壤玫,以便訂閱它。onRxAction() 方法將得到Dispatcher 發(fā)布的所有 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));
  }

因?yàn)?store 將接收所有類型的 actions断部,而我們只處理那些我們關(guān)心的猎贴。為了做到這一點(diǎn),我們檢索 actions 中數(shù)據(jù)并處理蝴光,然后發(fā)布一個(gè) RxStoreChange她渴,通知 views。使用方法 postChange() 發(fā)布 RxStoreChange蔑祟。這種方法發(fā)布的RxStoreChange 包含將一個(gè)給定的 store id (主要用于標(biāo)識(shí)那個(gè) store 發(fā)出的)和 RxAction趁耗。

最后一步是實(shí)現(xiàn)公共接口,提供的我們想要的數(shù)據(jù)疆虚。

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

views 可以在接收到一個(gè) RxStoreChange 時(shí)或者任何需要的時(shí)候苛败,調(diào)用該方法,得到 store 中的數(shù)據(jù)装蓬。

Wiki-4:Setup View

views 負(fù)責(zé)觸發(fā) actions著拭,注冊(cè) stores 并使用 stores 的數(shù)據(jù)。

第一步是項(xiàng)目中的每個(gè) activity 必須實(shí)現(xiàn) RxViewDispatch牍帚。

getRxStoreListToRegister() 告訴 RxFlux 哪個(gè) store 需要注冊(cè)儡遮。

  @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);
  }

我們?nèi)绾谓⑽覀兊纳痰辍T谶@個(gè)例子中暗赶,我們得到的需要注冊(cè)的 store 的實(shí)例鄙币。RxFlux 在 activity 創(chuàng)建成功之后會(huì)調(diào)用該方法,若 activity 是 RxViewDispatch 的子類蹂随,獲取需要注冊(cè)的 RxStoreList 并調(diào)用 RxStore 中的方法 register()十嘿,注冊(cè) store 到 Dispatcher 中。

Note: 為了安全岳锁,在多次調(diào)用注冊(cè)時(shí)绩衷,我們只注冊(cè)一次。

onRxStoreChanged() 在每次 store 發(fā)送一個(gè) RxStoreChange 的時(shí)候?qū)⒈徽{(diào)用激率。

  @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 有一個(gè) store 的標(biāo)識(shí)和一個(gè) RxAction咳燕。通過這兩個(gè),views 可以執(zhí)行期望的邏輯乒躺。

onRxError() 在 RxError 被 post 到 Dispatcher 時(shí)調(diào)用招盲。

  @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();
    }
  }

這種方法允許我們作出反應(yīng),當(dāng)我們得到一個(gè)錯(cuò)誤時(shí)嘉冒。注意 RxError 包含期望的 RxAction曹货。因此,我們可以使用它來重試或顯示適當(dāng)?shù)男畔ⅰ?/p>

RxViewDispatcher 的最后兩個(gè)方法是用來注冊(cè)其他非 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
  }

高級(jí)選項(xiàng)

RxFlux 將管理應(yīng)用程序生命周期顶籽,在正確的時(shí)候注冊(cè)和注銷 views玩般,避免內(nèi)存泄漏。如果應(yīng)用程序被destroyed礼饱,還將負(fù)責(zé)清理任何訂閱壤短。

有關(guān)更多信息,請(qǐng)查看 RxFlux.class (主類慨仿,負(fù)責(zé)連接每個(gè)部分,基于 Android 的生命周期)纳胧。

Flow

  1. RxFlux 會(huì)在 activity 創(chuàng)建時(shí)镰吆,注冊(cè)我們所需要的 store,調(diào)用
    getRxStoreListToRegister()跑慕。
  2. 在 activity resume 時(shí)万皿,RxFlux 將注冊(cè)這個(gè) activity 到 Dispatcher 中。
  3. 在 activity pause 時(shí)核行,RxFlux 從 Dispatcher 中解除這個(gè) activity 的注冊(cè)牢硅。
  4. 當(dāng)最后一個(gè) activity 被 destroy 時(shí),RxFlux 將調(diào)用關(guān)閉芝雪。

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

根據(jù) activity 的生命周期來處理注冊(cè)和解除注冊(cè)减余,這是正確的方法,可以避免當(dāng) view 不在前臺(tái)的時(shí)候惩系,獲取 RxStoreChange 并響應(yīng)觸發(fā) UI 變化位岔。
 
一個(gè)良好的實(shí)踐是在 onResume() 期間,檢查 store 的狀態(tài)并更新 UI堡牡。

公共方法

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

初始化 RxFlux 框架,必須在 Application 創(chuàng)建時(shí)調(diào)用此方法抒抬,并只有一次。

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

調(diào)用這個(gè)方法停止應(yīng)用程序晤柄,清理所有的訂閱和注冊(cè)擦剑。

  public RxBus getRxBus() {
    return rxBus;
  }

RxBus 的實(shí)例,如果您想在您的應(yīng)用程序中重用作為默認(rèn)的 bus system芥颈。

  public Dispatcher getDispatcher() {
    return Dispatcher;
  }

Dispatcher 的實(shí)例惠勒,負(fù)責(zé)處理訂閱,傳送 bus event 到正確的位置浇借。在創(chuàng)建 RxActionCreator 時(shí)捉撮,使用這個(gè)實(shí)例。

  public SubscriptionManager getSubscriptionManager() {
    return subscriptionManager;
  }

SubscriptionManager 的實(shí)例妇垢,以便你想要重用的別的東西巾遭。在創(chuàng)建 RxActionCreator 時(shí)肉康,使用這個(gè)實(shí)例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灼舍,一起剝皮案震驚了整個(gè)濱河市吼和,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骑素,老刑警劉巖炫乓,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異献丑,居然都是意外死亡末捣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門创橄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩做,“玉大人,你說我怎么就攤上這事妥畏“畎睿” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵醉蚁,是天一觀的道長(zhǎng)燃辖。 經(jīng)常有香客問我,道長(zhǎng)网棍,這世上最難降的妖魔是什么黔龟? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮确沸,結(jié)果婚禮上捌锭,老公的妹妹穿的比我還像新娘。我一直安慰自己罗捎,他們只是感情好观谦,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桨菜,像睡著了一般豁状。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒得,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天泻红,我揣著相機(jī)與錄音,去河邊找鬼霞掺。 笑死谊路,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菩彬。 我是一名探鬼主播缠劝,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼潮梯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了惨恭?” 一聲冷哼從身側(cè)響起秉馏,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脱羡,沒想到半個(gè)月后萝究,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锉罐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年帆竹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脓规。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡馆揉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抖拦,到底是詐尸還是另有隱情,我是刑警寧澤舷暮,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布态罪,位于F島的核電站,受9級(jí)特大地震影響下面,放射性物質(zhì)發(fā)生泄漏复颈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一沥割、第九天 我趴在偏房一處隱蔽的房頂上張望耗啦。 院中可真熱鬧,春花似錦机杜、人聲如沸帜讲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽似将。三九已至,卻和暖如春蚀苛,著一層夾襖步出監(jiān)牢的瞬間在验,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工堵未, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腋舌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓渗蟹,卻偏偏與公主長(zhǎng)得像块饺,于是被迫代替她去往敵國(guó)和親赞辩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,322評(píng)論 25 707
  • Android項(xiàng)目做了不少刨沦,難免遇到因?yàn)樵陧?xiàng)目架構(gòu)上設(shè)計(jì)不合理或者根本沒有形成統(tǒng)一的編程思想诗宣,導(dǎo)致各種意外的情況出...
    Forrest32閱讀 6,004評(píng)論 11 34
  • ##Flux與面向組件化開發(fā)首先要明確的是,F(xiàn)lux并不是一個(gè)前端框架想诅,而是前端的一個(gè)設(shè)計(jì)模式召庞,其把前端的一個(gè)交互...
    吳小蛆閱讀 316評(píng)論 0 0
  • 1.Json簡(jiǎn)介 Json,全名 JavaScript Object Notation来破,是一種輕量級(jí)的數(shù)據(jù)交換格式...
    herrykb閱讀 1,118評(píng)論 0 0
  • 中國(guó)的諺語常說篮灼,早睡早起身體好。但牛津大學(xué)有一位學(xué)者不這么認(rèn)為徘禁。 這位學(xué)者叫Paul Kelley诅诱,他去年的一項(xiàng)研...
    b7c2ae4ecd24閱讀 667評(píng)論 0 0