实践移动端的Flux架构

欢迎Follow我的GitHub, 关注我的简书. 其余参考Android目录.

架构

本文的合集已经编著成书,高级Android开发强化实战,欢迎各位读友的建议和指导。在京东即可购买:https://item.jd.com/12385680.html

Android

任何架构最终目的都是让程序更加有序, 功能便于扩展, Bug容易追踪.

Facebook使用Flux架构来构建客户端的Web应用. Flux架构并不是为移动端设计的, 但是我们仍然可以采用这个思想在Android端使用. Flux是数据驱动型架构, 在以数据为核心的场景中使用非常合适, 不过Facebook好像把Flux架构应用于所有产品, 无论是前端还是移动端. 最新Facebook开发的ReactNative中, 就是使用Flux架构为核心, 也是开源的, 可以阅读RN的代码了解所有内容.

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

本文源码的Github下载地址


基本架构模型如图:

架构

模型主要分为四个模块:
(1) View: 视图, 根据用户交互(Use Interaction)的内容创建响应事件, 调用活动创建器(ActionCreator)的方法, 发送响应用户操作的事件.
(2) ActionCreator: 活动创建器, 把活动的类型和数据发送给调度器(Dispatcher), 根据类型和数据创建活动(Action). 也可以进行网络操作(WebApi), 完成后, 再发送数据.
(3) Dispatcher: 调度器, 把接收到的活动, 通过总线(EventBus), 把活动发送出去, 由存储器(Store)根据活动的类型和数据, 修改数据模型.
(4) Store: 存储器, 维护特定的数据状态, 接收总线分发的活动, 根据活动类型执行不同的业务逻辑. 在完成时, 发出数据修改(ChangeEvent)的事件, 视图监听这一事件, 更新显示内容.

Talk is cheap, show you the code.
只有通过代码才能真正的了解架构的意义, 我写了一个基于Flux架构的ToDoList.
页面发送各种增删改查的事件, 通过Flux模型, 修改本地存储的数据, 再反馈回页面.

TodoList

1. 视图(View)

视图(View): 视图是一个Activity, 负责响应用户交互(Use Interaction)的事件, 发送到给活动创建器(ActionCreator); 同时接收修改数据(ChangeEvent)事件, 更新页面.

public class MainActivity extends AppCompatActivity {

    // ...

    // 添加数据
    @OnClick(R.id.main_add) void addItem() {
        addTodo(); // 添加TodoItem
        resetMainInput(); // 重置输入框
    }

    // 选中数据
    @OnClick(R.id.main_checkbox) void checkItem() {
        checkAll(); // 所有Item项改变选中状态
    }

    @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();

        // 把订阅接口注册到EventBus
        sDispatcher.register(this);
        sDispatcher.register(sTodoStore);
    }

    @Override
    protected void onPause() {
        super.onPause();

        // 解除订阅接口
        sDispatcher.unregister(this);
        sDispatcher.unregister(sTodoStore);
    }

    // 改变改变所有状态(ActionsCreator)
    private void checkAll() {
        sActionsCreator.toggleCompleteAll();
    }

    // 清理选中的项(ActionsCreator)
    private void clearCompleted() {
        sActionsCreator.destroyCompleted();
    }

    // ...

    // 接收事件的改变
    @Subscribe
    public void onTodoStoreChange(TodoStore.TodoStoreChangeEvent event) {
        updateUI();
    }

    // 更新UI, 核心方法
    private void updateUI() {
        // 设置适配器数据, 每次更新TodoStore的状态
        mListAdapter.setItems(sTodoStore.getTodos());
        ...
    }
}

代码比较长, 只截取了一部分. 其中View的核心部分:
(1) 活动创建器(ActionsCreator), 负责响应用户交互事件, 如checkAll()等.
(2) 接收状态修改(onTodoStoreChange), 负责接收存储器(Store)修改完成的数据, 并更新页面(updateUI).


2. 活动创建器(ActionsCreator)

活动创建器(ActionsCreator): 主要负责根据响应事件类型数据, 发送相应的活动到调度器(Dispatch). 也可以发送网络请求(WebApi), 获取异步数据, 完成后再发送.

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);
    }

    // ...
}

活动在调度器(Dispatcher)中创建.
活动创造器(ActionsCreator), 第一个参数是类型, 其余参数是数据, 即配对的Key-Value.


3. 调度器(Dispatcher)

调度器(Dispatcher), 是事件分发的中心, 使用事件总线(EventBus), 发送活动到存储器(Store). 存储器根据活动的类型和数据, 进行处理.

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), 发送至事件总线(EventBus), 由存储器(Store)负责处理.
(2) emitChange(): 把存储器(Store)处理完的状态和数据, 发送修改通知至事件总线(EventBus), 提示视图(View)进行更新页面(UpdateUI).


4. 存储器(Store)

存储器(Store): 负责存储数据和状态, 接收事件总线(EventBus)上的修改通知, 根据类型, 修改数据和状态. 也可以使用数据库和本地存储.

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;
    }

    @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;
            // ...
        }
    }

    private void destroyCompleted() {
        Iterator<Todo> iter = mTodos.iterator();
        while (iter.hasNext()) {
            Todo todo = iter.next();
            if (todo.isComplete()) {
                iter.remove();
            }
        }
    }

    // ...

    @Override
    public StoreChangeEvent changeEvent() {
        return new TodoStoreChangeEvent();
    }
}

存储器(Store)的核心是onAction()方法, 获得事件总线(EventBus)的通知, 修改数据, 完成后调用emitStoreChange()方法, 通知视图(View)进行数据更新(UpdateUI).


整套的循环逻辑都已经完成, 清晰可见, 这就是架构的好处吧.

Flux: View -> Action -> Dispatcher -> Store -> View.

想更多的了解Flux架构, Facebook参考.

OK, that's all! Enjoy it!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容