Android - Flux架构

jopen 8年前

Flux架构, 顾名思义表示, 是以数据流为基础.

任何架构最终的目的都是让程序更加有序, 功能便于扩展, Bug容易追踪.
非死book使用Flux架构来构建客户端Web应用. Flux架构并不是为移动端设计的, 但是我们仍然可以采用这个思想在Android端使用.


基本架构模型如图:
架构

模型主要分为四个模块:
1. View: 视图. 通过调用ActionCreator创建响应用户操作的Action.
2. Action: 事件. View通过ActionCreator发送至Dispatcher, Dispatcher创建Action并分发至EventBus.
3. Dispatcher: 调度器. ActionCreator调用Dispatcher, 传递创建的Action, 使用EventBus, 把Action送到Bus.
4. Store: 状态. 维护一个特定的数据状态, 接收Bus分发Action, 根据Action类型执行不同的业务逻辑. 在完成时, 发出一个ChangeEvent事件. View监听这一事件, 更新显示.


Talk is cheap, show you the code.
非常简单的一个TodoList的App, 数据仅仅只有一个Todo的数组.
项目代码: https://github.com/SpikeKing/MyFluxApp-TodoList
应用


1. View视图

package www.wangchenlong.me.myfluxapp;    import android.support.design.widget.Snackbar;  import android.support.v7.app.AppCompatActivity;  import android.os.Bundle;  import android.support.v7.widget.LinearLayoutManager;  import android.support.v7.widget.RecyclerView;  import android.text.TextUtils;  import android.view.Menu;  import android.view.MenuItem;  import android.view.View;  import android.view.ViewGroup;  import android.widget.CheckBox;  import android.widget.EditText;    import com.squareup.otto.Bus;  import com.squareup.otto.Subscribe;    import butterknife.Bind;  import butterknife.ButterKnife;  import butterknife.OnClick;  import www.wangchenlong.me.myfluxapp.actions.ActionsCreator;  import www.wangchenlong.me.myfluxapp.dispatcher.Dispatcher;  import www.wangchenlong.me.myfluxapp.stores.TodoStore;    /** * 主UI控件: ToDoList, 使用Flux架构 * Dispatcher调度器, Action事件, Store控制选择 * View调用Action事件, Action发送给Dispatcher进行调度, Dispatcher发送到EventBus * EventBus进行事件分发, 传递给Store, Store处理完成之后, 发送事件StoreChangeEvent, * 由EventBus找到主页面View进行更新UI. */  @SuppressWarnings("unused")  public class MainActivity extends AppCompatActivity {        private static Dispatcher sDispatcher;      private static ActionsCreator sActionsCreator;      private static TodoStore sTodoStore; // 数据存储器, 存储Todo数据的状态      private RecyclerAdapter mListAdapter;        @Bind((R.id.main_layout))      ViewGroup mMainLayout; // 主控件        @Bind(R.id.main_input)      EditText mMainInput; // 编辑控件        @Bind(R.id.main_list)      RecyclerView mMainList; // ListView        @Bind(R.id.main_checkbox)      CheckBox mMainCheck; // 选中按钮        // 添加按钮      @OnClick(R.id.main_add)      void addItem() {          addTodo(); // 添加TodoItem          resetMainInput(); // 重置输入框      }        // 选中按钮      @OnClick(R.id.main_checkbox)      void checkItem() {          checkAll(); // 所有Item项改变选中状态      }        // 清理完成事项      @OnClick(R.id.main_clear_completed)      void clearCompletedItems() {          clearCompleted(); // 清除选中的状态          resetMainCheck();      }        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main); // 设置Layout          ButterKnife.bind(this); // 绑定ButterKnife            initDependencies(); // 创建Flux的核心管理类            // 设置RecyclerView          mMainList.setLayoutManager(new LinearLayoutManager(this));          mListAdapter = new RecyclerAdapter(sActionsCreator);          mMainList.setAdapter(mListAdapter);      }        /** * Dispatcher调度器, Action事件, Store控制选择 */      private void initDependencies() {          sDispatcher = Dispatcher.getInstance(new Bus());          sActionsCreator = ActionsCreator.getInstance(sDispatcher);          sTodoStore = TodoStore.getInstance(sDispatcher);      }        @Override      protected void onResume() {          super.onResume();            // 把Subscribe的接口注册到EventBus上面          sDispatcher.register(this);          sDispatcher.register(sTodoStore);      }        @Override      protected void onPause() {          super.onPause();          sDispatcher.unregister(this);          sDispatcher.unregister(sTodoStore);      }        /** * 添加ToDo项, 向ActionsCreator传递输入文本. */      private void addTodo() {          if (validateInput()) {              sActionsCreator.create(getInputText());          }      }        /** * 重置输入框 */      private void resetMainInput() {          mMainInput.setText("");      }        /** * 改变改变所有状态(ActionsCreator) */      private void checkAll() {          sActionsCreator.toggleCompleteAll();      }        /** * 清理选中的项(ActionsCreator) */      private void clearCompleted() {          sActionsCreator.destroyCompleted();      }        /** * 全选中按钮的置换状态 */      private void resetMainCheck() {          if (mMainCheck.isChecked()) {              mMainCheck.setChecked(false);          }      }        /** * 更新UI */      private void updateUI() {            // 设置适配器数据, 每次更新TodoStore的状态          mListAdapter.setItems(sTodoStore.getTodos());            if (sTodoStore.canUndo()) { // 判断是否可恢复                // 下面的提示条, 恢复删除, 提示信息              Snackbar snackbar = Snackbar.make(mMainLayout, "Element deleted", Snackbar.LENGTH_LONG);                // 恢复按钮              snackbar.setAction("Undo", new View.OnClickListener() {                  @Override                  public void onClick(View view) {                      sActionsCreator.undoDestroy();                  }              });              snackbar.show();          }      }        // 验证输入框是否是空      private boolean validateInput() {          return !TextUtils.isEmpty(getInputText());      }        // 获取输入数据      private String getInputText() {          return mMainInput.getText().toString();      }        @Subscribe      public void onTodoStoreChange(TodoStore.TodoStoreChangeEvent event) {          updateUI();      }        @Override      public boolean onCreateOptionsMenu(Menu menu) {          // Inflate the menu; this adds items to the action bar if it is present.          getMenuInflater().inflate(R.menu.menu_main, menu);          return true;      }        @Override      public boolean onOptionsItemSelected(MenuItem item) {          // Handle action bar item clicks here. The action bar will          // automatically handle clicks on the Home/Up button, so long          // as you specify a parent activity in AndroidManifest.xml.          int id = item.getItemId();            //noinspection SimplifiableIfStatement          if (id == R.id.action_settings) {              return true;          }            return super.onOptionsItemSelected(item);      }  }

View中最核心的部分有两个:
(1) ActionsCreator负责创建Action, 所有方法都被封装在其中, 如ActionsCreator.toggleCompleteAll().
(2) onTodoStoreChange负责接收Store执行过业务逻辑之后的数据, 如TodoStore.getTodos(), 还可以根据状态进行修改, 如TodoStore.canUndo().


2. ActionsCreator事件生成器

package www.wangchenlong.me.myfluxapp.actions;    import www.wangchenlong.me.myfluxapp.dispatcher.Dispatcher;  import www.wangchenlong.me.myfluxapp.model.Todo;    /** * 事件发生器, 向Dispatcher发送事件类型, 和数据字典. * <p/> * Created by wangchenlong on 15/8/17. */  public class ActionsCreator {      private static ActionsCreator sInstance;      private final Dispatcher mDispatcher;        private ActionsCreator(Dispatcher dispatcher) {          mDispatcher = dispatcher;      }        public static ActionsCreator getInstance(Dispatcher dispatcher) {          if (sInstance == null) {              sInstance = new ActionsCreator(dispatcher);          }          return sInstance;      }        public void create(String text) {          mDispatcher.dispatch(                  TodoActions.TODO_CREATE,                  TodoActions.KEY_TEXT, text          );      }        public void destroy(long id) {          mDispatcher.dispatch(                  TodoActions.TODO_DESTROY,                  TodoActions.KEY_ID, id          );      }        public void undoDestroy() {          mDispatcher.dispatch(                  TodoActions.TODO_UNDO_DESTROY          );      }        public void toggleComplete(Todo todo) {          long id = todo.getId();          String actionType = todo.isComplete() ?                  TodoActions.TODO_UNDO_COMPLETE : TodoActions.TODO_COMPLETE;            mDispatcher.dispatch(                  actionType,                  TodoActions.KEY_ID, id          );      }        public void toggleCompleteAll() {          mDispatcher.dispatch(TodoActions.TODO_TOGGLE_COMPLETE_ALL);      }        public void destroyCompleted() {          mDispatcher.dispatch(TodoActions.TODO_DESTROY_COMPLETED);      }  }

ActionsCreator调用Dispatcher的dispatch(), 第一个参数是类型, 其余是数据, 两两组合Key-Value.


3. Dispatcher调度器

package www.wangchenlong.me.myfluxapp.dispatcher;    import com.squareup.otto.Bus;    import www.wangchenlong.me.myfluxapp.actions.Action;  import www.wangchenlong.me.myfluxapp.stores.Store;    /** * 调度器 * <p/> * Created by wangchenlong on 15/8/17. */  public class Dispatcher {        private final Bus mBus;      private static Dispatcher sInstance;        private Dispatcher(Bus bus) {          mBus = bus;      }        public static Dispatcher getInstance(Bus bus) {          if (sInstance == null) {              sInstance = new Dispatcher(bus);          }          return sInstance;      }        public void register(final Object cls) {          mBus.register(cls);      }        public void unregister(final Object cls) {          mBus.unregister(cls);      }        private void post(final Object event) {          mBus.post(event);      }        // 每个状态改变都需要发送事件, 由View相应, 做出更改      public void emitChange(Store.StoreChangeEvent o) {          post(o);      }        /** * 调度核心函数 * * @param type 调度类型 * @param data 数据(Key, Value) */      public void dispatch(String type, Object... data) {          if (type == null || type.isEmpty()) { // 数据空              throw new IllegalArgumentException("Type must not be empty");          }          if (data.length % 2 != 0) { // 非Key-Value              throw new IllegalArgumentException("Data must be a valid list of key,value pairs");          }            Action.Builder actionBuilder = Action.type(type);            int i = 0;          while (i < data.length) {              String key = (String) data[i++];              Object value = data[i++];              actionBuilder.bundle(key, value); // 放置键值          }            // 发送到EventBus          post(actionBuilder.build());      }    }

调度器代理了EventBus的功能, 有两个部分:
(1) dispatch()把数据转换成Action, 发送至Bus.
(2) emitChange()把修改后的状态通知发送至Bus, 由View接收负责处理.


4. Store状态

package www.wangchenlong.me.myfluxapp.stores;    import com.squareup.otto.Subscribe;    import java.util.ArrayList;  import java.util.Collections;  import java.util.Iterator;  import java.util.List;    import www.wangchenlong.me.myfluxapp.dispatcher.Dispatcher;  import www.wangchenlong.me.myfluxapp.actions.Action;  import www.wangchenlong.me.myfluxapp.actions.TodoActions;  import www.wangchenlong.me.myfluxapp.model.Todo;    /** * 状态类, 主要处理所有Todo列表事件的状态 * <p/> * Created by wangchenlong on 15/8/17. */  public class TodoStore extends Store {        private static TodoStore sInstance; // 单例      private final List<Todo> mTodos; // 数据列表      private Todo lastDeleted; // 最近一次删除数据        private TodoStore(Dispatcher dispatcher) {          super(dispatcher);          mTodos = new ArrayList<>();      }        public static TodoStore getInstance(Dispatcher dispatcher) {          if (sInstance == null) {              sInstance = new TodoStore(dispatcher);          }            return sInstance;      }        // 获取数据      public List<Todo> getTodos() {          return mTodos;      }        // 恢复      public boolean canUndo() {          return lastDeleted != null;      }        @Override      @Subscribe      public void onAction(Action action) {          long id;          switch (action.getType()) {              case TodoActions.TODO_CREATE:                  String text = ((String) action.getData().get(TodoActions.KEY_TEXT));                  create(text);                  emitStoreChange(); // 发生改变事件                  break;                case TodoActions.TODO_DESTROY:                  id = ((long) action.getData().get(TodoActions.KEY_ID));                  destroy(id);                  emitStoreChange();                  break;                case TodoActions.TODO_UNDO_DESTROY:                  undoDestroy();                  emitStoreChange();                  break;                case TodoActions.TODO_COMPLETE:                  id = ((long) action.getData().get(TodoActions.KEY_ID));                  updateComplete(id, true);                  emitStoreChange();                  break;                case TodoActions.TODO_UNDO_COMPLETE:                  id = ((long) action.getData().get(TodoActions.KEY_ID));                  updateComplete(id, false);                  emitStoreChange();                  break;                case TodoActions.TODO_DESTROY_COMPLETED:                  destroyCompleted();                  emitStoreChange();                  break;                case TodoActions.TODO_TOGGLE_COMPLETE_ALL:                  updateCompleteAll();                  emitStoreChange();                  break;            }        }        private void destroyCompleted() {          Iterator<Todo> iter = mTodos.iterator();          while (iter.hasNext()) {              Todo todo = iter.next();              if (todo.isComplete()) {                  iter.remove();              }          }      }        private void updateCompleteAll() {          if (areAllComplete()) {              updateAllComplete(false);          } else {              updateAllComplete(true);          }      }        private boolean areAllComplete() {          for (Todo todo : mTodos) {              if (!todo.isComplete()) {                  return false;              }          }          return true;      }        private void updateAllComplete(boolean complete) {          for (Todo todo : mTodos) {              todo.setComplete(complete);          }      }        private void updateComplete(long id, boolean complete) {          Todo todo = getById(id);          if (todo != null) {              todo.setComplete(complete);          }      }        private void undoDestroy() {          if (lastDeleted != null) {              addElement(lastDeleted.clone());              lastDeleted = null;          }      }        private void create(String text) {          long id = System.currentTimeMillis();          Todo todo = new Todo(id, text);          addElement(todo);          Collections.sort(mTodos);      }        private void destroy(long id) {          Iterator<Todo> iter = mTodos.iterator();          while (iter.hasNext()) {              Todo todo = iter.next();              if (todo.getId() == id) {                  lastDeleted = todo.clone();                  iter.remove();                  break;              }          }      }        private Todo getById(long id) {          Iterator<Todo> iter = mTodos.iterator();          while (iter.hasNext()) {              Todo todo = iter.next();              if (todo.getId() == id) {                  return todo;              }          }          return null;      }        // 添加数据进入列表      private void addElement(Todo clone) {          mTodos.add(clone);          Collections.sort(mTodos);      }        @Override      public StoreChangeEvent changeEvent() {          return new TodoStoreChangeEvent();      }        public class TodoStoreChangeEvent implements StoreChangeEvent {      }  }

Store最为复杂, onAction()监听Bus所有的Action, 并处理业务逻辑, 最后调用emitStoreChange, 通知页面进行修改.


目前为止, 一整套的循环逻辑都已经完成, 清晰可见, 这就是架构的好处吧.
View -> Action -> Dispatcher -> Store -> View.


想更多的了解Flux架构:
https://非死book.github.io/flux/docs/overview.html
http://lgvalle.xyz/2015/08/04/flux-architecture/

To enjoy it!

来自: http://blog.csdn.net//caroline_wendy/article/details/47783525