Android - Flux架構

jopen 8年前發布 | 13K 次閱讀 Android開發 移動開發

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

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