Android Fluxアーキテクチャの初探査


序文
以前Androidプロジェクトがどのように構築されているかについて書いたことがありますが、MVCとMCVPがあります.先日また新しいアーキテクチャを見ましたが、もちろん新しく出たわけではありません.しばらく出てきましたが、現在応用されているのは一般的ではありません.次に、その特徴と使用からFluxアーキテクチャを紹介します.本稿の主な目的は、Fluxのアーキテクチャの概要を理解することです.
Fluxアーキテクチャの紹介
FluxアーキテクチャはFacebookによって使用され、クライアントのwebアプリケーションを構築します.Clean Architectureと同様に、モバイルアプリケーションのために設計されていませんが、その特性と簡単さはアンドロイドプロジェクトでよく採用することができます.Fluxを理解するには、2つの重要な特徴があります.
  • データストリームは常に一方向のデータストリームであり、Fluxアーキテクチャの核心であり、簡単で学びやすい理由でもある.以下で説明するように、アプリケーションテストを行う際に非常に役立ちます.
  • アプリケーションは、3つの主要な部分に分けられます.
  • View:アプリケーションのインタフェース.ここでは、ユーザ操作に応答するactionを作成します.
  • Dispatcher:ハブ、すべてのactionを伝え、各Storeに運ぶ責任を負います.
  • Store:特定のアプリケーションdomainのステータスを維持します.現在のステータスに基づいてactionに応答し、ビジネスロジックを実行し、完了時にchangeイベントを発行します.このイベントはviewのインタフェースを更新するために使用されます.
  • の3つの部分は、アクションによって通信されます.単純な基本オブジェクトで、操作に関連するデータを含むタイプで区別されます.


  • Flux Androidアーキテクチャ
    Android開発でFlux設計仕様を使用する目的は、シンプルさと拡張性とテスト性のバランスのとれたアーキテクチャを構築することです.最初のステップは、Flux要素とアンドロイドappコンポーネント間のマッピングを見つけることです.これらの2つの要素は、容易に見つけられ、実装されます.View:ActivityまたはFragmentDispatcher:イベントバス(event bus)は、私の例ではOttoを使用しますが、他の実装はokである必要があります.ActionsActionsも複雑ではありません.それらの実装はPOJOと同様に簡単で、2つの主要な属性がある:Type:1つのString、イベントのタイプを定義した.Data:mapで、今回の操作をマウントしました.StoreはFlux理論の中で最も難しい部分です.StoresはDispatcherが発行したActionに応答してビジネスロジックを実行しchangeイベントを送信する.Storesの唯一の出力は、この単一のイベント:changeです.Storeの内部状態に関心のある他のコンポーネントは、このイベントをリスニングし、必要なデータを取得する必要があります.最後に、storesはアプリケーションステータスを取得するインタフェースを外部に開示しなければならない.これにより、view要素は、Storesを問合せ、UIを更新することができる.
    ここでは簡単なdemoでプロセス全体を説明します.私たちのインタフェースにはButtonとTextViewがあり、ButtonをクリックしてTextViewに文字を表示させます.従来の実装は,Activityで直接論理,MVPモードを完成させ,Presenter層で行い,Fluxアーキテクチャについてはどのように実現するのか.上の図から、ViewはActionを生成し、Dispatcherによってスケジューリングされ、Storeの対応する処理を経てデータが表示されることがわかります.
  • Action
  • の生成方法
    まずアクションがどうなっているかを知る
    public class Action {
    
        private final String type;
        private final HashMap data;
    
        public Action(String type, HashMap data) {
            this.type = type;
            this.data = data;
        }
    
        public static Builder type(String type) {
            return  new Builder().with(type);
        }
    
        public String getType() {
            return type;
        }
    
        public HashMap getData() {
            return data;
        }
    
        public static class Builder {
            private String type;
            private HashMap data;
    
            Builder with(String type) {
                if(type == null) {
                    throw  new IllegalArgumentException("Type may not be null.");
                }
                this.type = type;
                this.data = new HashMap<>();
                return this;
            }
    
            public Builder bundle(String key, Object value) {
                if (key == null) {
                    throw  new IllegalArgumentException("Key may not be null.");
                }
                if(value == null) {
                    throw new IllegalArgumentException("Value may not be null.");
                }
                data.put(key, value);
                return this;
            }
    
            public Action build() {
                if (TextUtils.isEmpty(type)) {
                    throw  new IllegalArgumentException("At least one key is required.");
                }
                return new Action(type, data);
            }
        }
    }
    

    各アクションには2つの属性があり、1つはTypeをマークし、もう1つのフィールドは転送されたデータを格納し、Mapによって格納します.ActionTypeでは、1つのインタフェースまたはクラスで記録し、すべてのタイプを保存できます.私たちの呼び出しを便利にします.
    public interface ShowActions {
        String TODO_SHOW = "todo-show";
        String GET_TEXT = "get-text";
    }

    アクションを作成する方法、クラスを定義する方法、私たちが現れる可能性のあるさまざまなViewのイベントに基づいて、さまざまなアクションを定義するために使用されます.
    public class ActionsCreator {
    
        private  static ActionsCreator instance;
    
        final Dispatcher mDispatcher;
    
        ActionsCreator(Dispatcher dispatcher){
            mDispatcher = dispatcher;
        }
    
        public static ActionsCreator get(Dispatcher dispatcher) {
            if (instance == null) {
                instance = new ActionsCreator(dispatcher);
            }
            return instance;
        }
    
        public void create(String text) {
            mDispatcher.dispatch(ShowActions.TODO_SHOW, ShowActions.GET_TEXT, text);
        }

    ActionsCreatorでActionを作成する準備をしていたとき、直接new Actionという方法ではなく、スケジューラを通じて配布しました.ここでのイベント配信は、OttoのBusを使用してイベント配信を行います.
    public class Dispatcher {
    
        private final Bus bus;
        private static Dispatcher instance;
    
        Dispatcher(Bus bus){
            this.bus = bus;
        }
    
        public static  Dispatcher get(Bus bus) {
            if (instance == null) {
                instance = new Dispatcher(bus);
            }
            return instance;
        }
    
        public void register(final Object cls) {
            bus.register(cls);
        }
    
        public void unRegister(final Object cls) {
            bus.unregister(cls);
        }
    
        public void emitChange(Store.StoreChangeEvent o) {post(o);}
    
        public void dispatch(String type, Object... data) {
            if(TextUtils.isEmpty(type)) {
                throw new IllegalArgumentException("Type must not be empty");
            }
    
            if (data.length % 2 != 0) {
                throw  new IllegalArgumentException("Data must be a valid list of key");
            }
    
            Action.Builder actionBuilder = Action.type(type);
            for (int i = 0; i < data.length; i++) {
                String key = (String) data[i++];
                Object value = data[i++];
                actionBuilder.bundle(key, value);
            }
            post(actionBuilder.build());
        }
    
        private boolean isEmpty(String type) {
            return TextUtils.isEmpty(type);
        }
    
        private void post(final Object event) {
            bus.post(event);
        }
    }
    

    スケジューリングの過程で、伝達されたデータを解析し、データに基づいて対応するActionを作成し、Actionを配布します.このとき、対応するActionに注目したStoreは、対応するActionに基づいて対応する操作を開始します.Storeでは、アクションに対する判断と配布を担当する抽象メソッドonActionを宣言し、StoreChangeEventインタフェースをイベント変化として定義し、変化がある場合、これを伝達することで、私たちは自分でこのインタフェースを実現し、データを携帯するためにいくつかのメソッドとフィールドを追加することができます.
    public abstract class Store {
        final Dispatcher mDispatcher;
    
        protected Store(Dispatcher dispatcher) {
            this.mDispatcher = dispatcher;
        }
    
        void emitStoreChange() {
            mDispatcher.emitChange(changeEvent());
        }
    
        abstract StoreChangeEvent changeEvent();
    
        public abstract void onAction(Action action);
    
        public interface StoreChangeEvent {}
    
    }

    カスタムStoreクラス
    public class ShowStore extends Store {
    
        private static ShowStore instance;
        private String showText;
    
        public ShowStore(Dispatcher dispatcher){
            super(dispatcher);
        }
    
        public static ShowStore get(Dispatcher dispatcher) {
            if (instance == null) {
                instance = new ShowStore(dispatcher);
            }
            return instance;
        }
    
        @Subscribe
        public void onAction(Action action) {
            switch (action.getType()) {
                case ShowActions.TODO_SHOW :
                    showText = ((String)action.getData().get(ShowActions.GET_TEXT));
                    Log.i("showText", showText);
                    emitStoreChange();
                    break;
                default:
                    break;
            }
        }
    
        public String getShowText(){
            return showText;
        }
    
        @Override
        StoreChangeEvent changeEvent() {
            return new ShowChangeEvent();
        }
    
        public class ShowChangeEvent implements StoreChangeEvent {
    
        }
    }

    その後、View、つまりActivityで変化時間の方法を購読し、このときにViewのデータの動的更新を実現することができます.
      @Subscribe
        public void showText (ShowStore.ShowChangeEvent event){
            mTextView.setText(mShowStore.getShowText());
        }

    アーキテクチャの観点から見るFlux
    全体のアーキテクチャの観点から見ると、私たちのActivity、Fragmentでは、私たちのViewが非常に豊富で、論理が非常に多い場合、MVPのアーキテクチャの下で、P層は非常に肥大化し、後期のメンテナンスは難しくなります.私たちはそれを複数のp層に分割することができます.画面全体の異なるViewをそれぞれ処理するために使用され、Viewのイベントに応答し、対応する論理を処理する責任を負います.もう一つの問題は、私たちの異なるモデルでは、いくつかの同じ操作があるかもしれませんが、私たちはそれを異なるmoduleの下のP層に置く必要があります.Flux方式では、これらの共通の論理を完全にデカップリングすることができます.Storeに格納し、一方向のデータストリームで処理するデータをStoreに渡し、処理後の結果を返します.
    まとめ
    Fluxアーキテクチャによると、我々のViewのイベントはデータを携帯し、ActionsCreateを通じてTypeのActionを作成し、実際の完了プロセスはDispatcherのdispatchにあり、その後、このActionをActionを購読したStoreメソッドに捨て、ここで様々な論理を完了し、処理を完了し、さらにはネットワーク要求を開始してデータを取得することができる.処理が完了すると、結果をイベントにカプセル化することができ、このイベントはスケジューラのemitChangeEventを介してイベントをサブスクリプションした関数に再び渡され、この応答イベントを受信する関数は私たちのViewに定義され、私たちのViewに対する更新を実現します.