Android Flux初體驗

sky12l 8年前發布 | 7K 次閱讀 安卓開發 Android開發 移動開發

前言

  • 在一次日常逛知乎的時候發現了Android Flux,相比與之前的開發,自己還從未接觸到這樣的開發模式,于是經過查閱資料后,自己動手嘗試體驗了一番,并加以總結記錄。

簡介

  • Flux源于非死book在14年提出的一種Web前端架構,主要是用來處理復雜的UI邏輯的一致性問題。經過實踐后發現,這種架構可以很好的應用于Android平臺,相對于其他的MVC/MVP/MVVM等模式,擁有良好的文檔和更具體的設計,比較適合于快速開發實現。

核心思想

單向數據流

Android Flux

  • 如圖所示,Flux的核心思想是單向的數據流,所謂單向數據流,就是當用戶進行操作的時候,會從View層發出一個Action,這個Action通過Dispatcher流向Store里面,觸發Store對狀態進行改動,然后再由Store觸發新的狀態通知到View進行重新渲染。

Action

  • 用戶操作界面時會觸發一個Action,這個Action即是數據的封裝,Dispatcher會將這個Action分發到Store
  • Action的創建一般被封裝到一個有語義的Helper類,( ActionCreator ),所謂有語義,就是根據不同的業務創建不同的Action,然后把這個Action傳遞到Dispatcher,進行Action的分發

Dispatcher

  • 一個應用中只有一個Dispatcher,Dispatcher管理所有的數據流,(即內部管理的是所有的Store),它是Action與Store聯系的中心樞紐
  • 實際上,它管理的是Store注冊的一系列回調接口,本身沒有其他的邏輯,僅僅是把Action分發到Store
  • 由此可知,所有的數據流必須從這里經過,依次分發到已注冊的Store中

Store

  • Store包含應用的狀態和邏輯,通常在此類中實現對Action的邏輯處理,并把處理的結果通知到View中
  • Store會把自己注冊在Dispatcher上并提供一個回調的接口,用于處理Action,當Dispatcher分發Action時,內部管理的Store就會回調這個用于處理Action的接口
  • 通過Dispatcher發送Action來更新Store的內部狀態,當Store更新后,它會發送一個事件聲明自己的狀態已經發送了改變,(通常是View接收),然后View會讀取這些變化并更新自己

View

  • 即是Activity和Fragment,負責監聽Store發送的事件并更新界面
  • 通常一個Activity對應一個Store,但是如果Activity包含許多Fragment,也可以讓每個Fragment對應自己的Store

代碼實踐

  • 本項目模擬了一個登陸的過程,Activity由兩個EditText和一個Button組成,當username和password均為123時,模擬登陸成功;否則模擬登陸失敗。

Flux Test

Action

/**

  • 簡單的POJO類型,只提供兩個字段:type 和 data, 分別記錄Action的類型和數據
  • Created by ckerv on 16/12/4. */ public class Action<T> {

    private String type;

    private T data;

    public Action(String type, T data) {

     this.type = type;
     this.data = data;
    

    }

    public String getType() {

     return type;
    

    }

    public void setType(String type) {

     this.type = type;
    

    }

    public T getData() {

     return data;
    

    }

    public void setData(T data) {

     this.data = data;
    

    } }</code></pre>

    /**

  • Created by ckerv on 16/12/5. */ public class LoginAction extends Action<LoginBean> {

    public static final String LOGIN_ACTION = "login.action";

    public LoginAction(String type, LoginBean data) {

     super(type, data);
    

    } }</code></pre>

    • Action是簡單的POJO類型,采用泛型對數據進行封裝,只提供了兩個字段 type 和 data ,分別記錄Action的類型和數據
    • LoginAction是Action的具體業務實現,實現非常簡單,只添加了一個Action類型字段 LOGIN_ACTION

    Dispatcher

    /**

  • 一個app通常只有一個Dispatcher類,內部進行對Store的管理
  • 此類的作用是將action分發到store,是連接action和store的中心樞紐
  • Created by ckerv on 16/12/4. */ public class Dispatcher {

    private static Dispatcher INSTANCE;

    private List<Store> mStores;

    private Dispatcher() {

     mStores = new ArrayList<>();
    

    }

    public static Dispatcher getInstance() {

     if(INSTANCE == null) {
         synchronized (Dispatcher.class) {
             if(INSTANCE == null) {
                 INSTANCE = new Dispatcher();
             }
         }
     }
     return INSTANCE;
    

    }

    /**

    • 注冊store,即把store添加到list中
    • @param store */ public void register(Store store) { this.mStores.add(store); }

      /**

    • 注銷store,從list中移除
    • @param store */ public void unRegister(Store store) { this.mStores.remove(store); }

      /**

    • 分發action
    • @param action */ public void dispatch(Action action) { post(action); }

      /**

    • 向注冊的store list依次分發acion
    • @param action */ private void post(Action action) { for (Store store : mStores) {
       store.onAction(action);
      
      } } }</code></pre>
      • Store會在這里注冊自己的回調接口,Dispatcher會把Action分發到注冊的Store,所以它會提供一些公有方法來注冊監聽和分發消息
      • Dispatcher對外僅暴露3個公有方法:
        • register(final Store store) 用來注冊每個Store的回調接口
        • unregister(final Store store) 用來接觸Store的回調接口
        • dispatch(Action action) 用來觸發Store注冊的回調接口
        </li>
      • 用一個ArrayList來管理Stores,對于一個更復雜的app可能需要精心設計store所管理的數據結構
      • </ul>

        Store

        /**
        
    • 處理action的基類,通常可根據不同的業務邏輯實現{@link #onAction(Action)}
    • 通過EventBus傳遞數據
    • Created by ckerv on 16/12/4. */ public abstract class Store {

      private static final EventBus mBus = EventBus.getDefault();

      protected Store() {

      }

      public void register(Object view) {

       this.mBus.register(view);
      

      }

      public void unRegister(Object view) {

       this.mBus.unregister(view);
      

      }

      /**

      • post事件到view層,進行UI的后續處理 */ protected void emitStoreChange() { this.mBus.post(changeEvent()); }
    /**
     * 處理Action的邏輯,子類必須實現此方法
     * @param action
     */
    public abstract void onAction(Action action);
    
    /**
     * post事件
     * @return
     */
    protected abstract StoreChangeEvent changeEvent();
    
    public class StoreChangeEvent{}
    

    }</code></pre>

    • 這里Store是個抽象類,處理具體業務邏輯的子類必須實現 onAction(Action action) 和 changeEvent() 方法
    • 采用EventBus來向view發送事件,因此必須提供 register(Object view) 和 unRegister(Object view) 方法給外部注冊和注銷EventBus

    LoginStore

    /**

    • Created by ckerv on 16/12/5. */ public class LoginStore extends Store {

      public LoginStore() {

       super();
       loginResponseBean = new LoginResponseBean();
      

      }

      private LoginResponseBean loginResponseBean;

      public LoginResponseBean getLoginResponseBean() {

       return loginResponseBean;
      

      }

      @Override public void onAction(Action action) {

       switch (action.getType()) {
           case LoginAction.LOGIN_ACTION:
               LoginBean loginBean = (LoginBean) action.getData();
               if(loginBean.getUserName().equals("123") && loginBean.getPassWord().equals("123")) {
                   loginResponseBean.setSuccess(true);
               } else {
                   loginResponseBean.setSuccess(false);
               }
               break;
           default:break;
       }
       emitStoreChange();
      

      }

      @Override protected StoreChangeEvent changeEvent() {

       return new StoreChangeEvent();
      

      } }</code></pre>

      • 處理登錄邏輯的LoginStore類,繼承自Store,可以看到,在 onAction(Action) 進行登錄邏輯的驗證
      • 在登錄邏輯的處理過程中,通過改變 LoginReponseBean 的狀態來區分登錄成功與否,處理完邏輯之后,調用父類的 emitStoreChange() 方法發送事件通知view更新
      • 請注意,這里只提供LoginReponseBean的 get 方法,Store內部狀態的更新只能是通過Dispatcher改變

      MainActivity

      public class MainActivity extends AppCompatActivity implements View.OnClickListener{

      private EditText mEtUsername; private EditText mEtPassword; private Button mBtnLogin;

      private ActionCreator mActionCreator; private Dispatcher mDispatcher; private LoginStore mStore;

      @Override protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       initVariables();
       initViews();
      
      

      }

      private void initVariables() {

       mStore = new LoginStore();
       mDispatcher = Dispatcher.getInstance();
       mActionCreator = ActionCreator.getInstance(mDispatcher);
       mDispatcher.register(mStore);
      

      }

      @Override protected void onResume() {

       super.onResume();
       mStore.register(this);
      

      }

      @Override protected void onDestroy() {

       super.onDestroy();
       mStore.unRegister(this);
      

      }

      private void initViews() {

       mEtUsername = (EditText) findViewById(R.id.main_et_username);
       mBtnLogin = (Button) findViewById(R.id.main_btn_login);
       mEtPassword = (EditText) findViewById(R.id.main_et_password);
       mBtnLogin.setOnClickListener(this);
      

      }

      @Override public void onClick(View v) {

       switch (v.getId()) {
           case R.id.main_btn_login :
               mActionCreator.login(mEtUsername.getText().toString(), mEtPassword.getText().toString());
               break;
           default : break;
       }
      

      }

      @Subscribe public void onChangeEvent(Store.StoreChangeEvent changeEvent) {

      if(mStore.getLoginResponseBean().isSuccess()) {
          Toast.makeText(MainActivity.this, "登錄成功", Toast.LENGTH_LONG).show();
      } else {
          Toast.makeText(MainActivity.this, "登錄失敗", Toast.LENGTH_LONG).show();
      }
      

      } }</code></pre>

      • 一般說來,一個Activity有多少個業務就有多少個Store
      • 在 initVariables() 進行Store、Dispatcher和ActionCreator的初始化和注冊
      • 在 onResume() 和 onDestroy() 方法里進行EventBus的注冊和注銷
      • 當用戶點擊登錄按鈕時,由ActionCreator調用具體的業務方法 login(String username, String password) 使Dispatcher分發LoginAction到LoginStore,當LoginStore處理完LoginAction發送事件到MainActivity,MainActivity層接收到事件,獲取LoginStore的狀態,更新界面。

      流程總結

      流程總結

      小結

      • 以上是我對Android Flux開發模式的第一次實踐,可以看到,采用Android Flux的開發模式有如下好處:
        • View層只負責渲染界面和觸發Action,具體業務邏輯由Store實現,高度解耦
        • 要理解一個Store可能發生的狀態變化,只看 onAction(Action action) 中的邏輯處理即可
        • 由于數據(Action)是單向流動,因此Debug的時候變得輕松很多,可以快速定位Bug的發生地點
      • 以上是個人對于Anroid Flux的理解 ,有不足之處望指出一起討論,一起學習~O(∩_∩)O哈哈

      關于Android Flux

       

      來自:http://www.jianshu.com/p/31a071f6bc62

       

 本文由用戶 sky12l 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!