Android - Flux架構
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