android Flux架构初探

0.24字数 1816阅读 351

Flux架构初探

引用facebook官网的一段介绍:

Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.

Google翻译一下:

Flux是Facebook用于构建客户端Web应用程序的应用程序体系结构。 它通过利用单向数据流来补充React的可组合视图组件。 它更像是一种模式,而不是一个正式的框架,您可以立即开始使用Flux,而无需使用大量新代码。

Flux本身是facebook推出来的一个前端设计框架,但是虽然领域不一样,代码设计思想还是相同的,对于里面的一些优秀的设计思想我们还是可以借鉴一下。

在Flux中,单项数据流是flux的核心设计思想,我们可以看一下官方给到的flux模型图:


模型图

image

我们先介绍一下图中这几个成员:

  • Action

<font color='gray'>action类似于一个事件,包括但不仅限于一个用户交互操作,还包括一个后台推送,或者是一个定时器触发的操作,这都是一个action,每个action都有一个属于自己的事件id,通过这个id标明自己的身份,这样在整个流程中能够被传递到正确的位置。action在整个流程中扮演着一个信息传递者,内部携带着相关信息,推动流程的前进</font>

  • Dispatcher

<font color='gray'>Dispatcher是一个事件分发器,向外暴露注册接口,Store可以通过接口注册到Dispatcher中。Dispatcher内部包含着将Action发送到需要的Store中的逻辑</font>

  • Store

<font color='gray'>数据中心,我把它看成一个内存中的数据库,内部存储着相关的应用数据和一些状态,对外只暴露get接口而不暴露set接口,数据的改变只能通过action的驱动,以保持内部数据的准确性。当Store中的数据发生了改变,Store将会通知相对应的ui页面进行修改内容,从而减少ui界面的工作</font>

  • View

<font color='gray'>用户交互页面,在flux中view的工作只包括通过用户交互产生相对应的事件,以及当Store数据发生变化时收到通知修改自己的ui和状态。这样view层的工作就十分简单,只有和用户交互以及刷新ui界面</font>

在这些类中间还有一个类:ActionCreator

ActionCreator的工作就如同它的名字,创建action,只不过在创建action之前可能需要先进行网络请求等操作。

下面我们来对比一下使用MVP和使用Flux的一个登陆界面的逻辑:

public class FluxLoginActivity extends AppCompatActivity implements View.OnClickListener,IDataChangedListener {

    private EditText mEtAuthCode;
    private EditText mEtAccount;
    private LoginActionsCreator mCreator;
    private LoginStore mLoginStore;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        //将Store注册到Dispatcher中
        mLoginStore = new LoginStore();
        Dispatcher.getSingleton().register(mLoginStore);
        //设置回调接口
        mLoginStore.setDataChangedListener(this);

        mCreator = new LoginActionsCreator();

        Button btnLogin = findViewById(R.id.btn_login);
        mEtAccount = findViewById(R.id.et_account);
        mEtAuthCode = findViewById(R.id.et_authcode);
        Button btnGetCode = findViewById(R.id.btn_get_code);
        btnLogin.setOnClickListener(this);
        btnGetCode.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_get_code:
                mCreator.createGetAuthCodeAction(mEtAccount.getText().toString());
                break;
            case R.id.btn_login:
                //登陆之前确保已经成功获取验证码
                if (mLoginStore.isGetAuthCodeSuccess()){
                    mCreator.createLoginAction(mEtAccount.getText().toString(), mEtAuthCode.getText().toString());
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onDataChanged(String label) {
        switch (label) {
            case "AUTH_CODE":
                onAuthCodeResponse();
                break;
            case "LOGIN":
                onLoginResponse();
                break;
            default:
                break;
        }
    }

    private void onLoginResponse() {
        Exception exception = mLoginStore.getException();
        if (exception != null){
            //登录失败
            //弹窗或者吐司等提示用户
            return;
        }
        //登陆成功
        LoginResult loginResult = mLoginStore.getLoginResult();
        //****登陆成功之后的操作
    }

    /**
     * 获取验证码结果
     */
    private void onAuthCodeResponse() {
        Exception exception = mLoginStore.getException();
        if (exception != null){
            //获取验证码失败
            //弹窗或者吐司等提示用户
        }
    }
}

在flux的整个流程中,Action这个类一直是贯穿整个流程的,每个环节的交互打都离不开这个Action,View通过Creator创建Aciton,Dispatcher将Action发送给Store,Store关注自己感兴趣的事件ID,并在收到Aciton的时候修改自己的数据然后通知View,View再根据事件ID更新ui。

这个过程中,每个类都只做自己的事情,逻辑非常清晰。View负责和用户交互,用户通过点击按钮产生两个事件,“LOGIN”和“GET_AUTH_CODE”,然后Creator负责创建Action,里面包括获取Action应该携带的信息,在这里的体现就是网络请求。,然后Action将携带的数据通过Dispatcher发出去给到相对应的Store,Store负责修改状态和数据,然后再通知View更新ui。每个人都是做自己的事情,各司其职。

flux登录流程涉及到的类:

Dispatcher

public class Dispatcher {

    private static volatile Dispatcher dispatcher;

    /**
     * key: 事件id
     * List<BaseStore> 对该事件感兴趣的Store集合
     */
    private Map<String, List<BaseStore>> mStoreMap;

    public static Dispatcher getSingleton() {
        if (dispatcher == null) {
            synchronized (Dispatcher.class) {
                if (dispatcher == null) {
                    dispatcher = new Dispatcher();
                }
            }
        }
        return dispatcher;
    }

    private Dispatcher() {
        mStoreMap = new HashMap<>();
    }

    public void register(BaseStore store) {
        if (store == null) {
            return;
        }
        List<String> labels = store.getActionLabels();
        for (String label : labels) {
            List<BaseStore> stores = mStoreMap.get(label);
            if (stores == null) {
                stores = new ArrayList<>();
                mStoreMap.put(label, stores);
            }
            //防止重复注册
            if (stores.contains(store)) {
                continue;
            }
            stores.add(store);
        }
    }

    public void unregister(BaseStore store) {
        if (store == null) {
            return;
        }
        List<String> labels = store.getActionLabels();
        for (String label : labels) {
            if (!mStoreMap.containsKey(label)) {
                continue;
            }
            List<BaseStore> baseStores = mStoreMap.get(label);
            if (baseStores.remove(store)) {
                if (baseStores.isEmpty()) {
                    mStoreMap.remove(label);
                }
            }
        }
    }

    public void dispatch(BaseAction action) {
        final String label = action.getLabel();
        final List<BaseStore> stores = mStoreMap.get(label);
        if (stores == null) {
            return;
        }
        for (BaseStore store : stores) {
            store.changeData(action);
            store.notifyDataChanged(label);
        }
    }

    private void mainDispatch(final BaseAction action) {
        final String label = action.getLabel();
        final List<BaseStore> stores = mStoreMap.get(label);
        if (stores == null) {
            return;
        }
        for (BaseStore store : stores) {
            store.changeData(action);
            store.notifyDataChanged(label);
        }
    }
}

Dispatcher内部提供注册和解除注册的功能,内部维护一个Map,以事件Id为key,List<? extends BaseStore>为Value,之所以这么设计是因为对于一个事件可能不止一个Store关注,而且一个Store也可能关注很多事件。

loginAction & AuthCodeAction

class LoginAction extends BaseAction {
    //登录结果
    private LoginResult mResult;

    public LoginAction(LoginResult result) {
        super("LOGIN");
        mResult = result;
    }

    public LoginAction(Exception e) {
        super("LOGIN");
        setException(e);
    }

    public LoginResult getResult() {
        return mResult;
    }
}

public class AuthCodeAction extends BaseAction {
    //是否获取验证码成功
    private boolean isSuccess;

    public AuthCodeAction(boolean result) {
        super("AUTH_CODE");
        isSuccess = result;
    }

    public AuthCodeAction(Exception e){
        super("AUTH_CODE");
        setException(e);
    }

    public boolean isSuccess() {
        return isSuccess;
    }
}

Action中除了时间ID,还有就是携带的数据,包括isSuccess和loginResult。

LoginActionsCreator

class LoginActionsCreator {

    public void createGetAuthCodeAction(String account){
        /*
         * 这里是网络请求,这里直接模拟数据就不做请求了
         */
        AuthCodeAction authCodeAction = new AuthCodeAction(true);
        Dispatcher.getSingleton().dispatch(authCodeAction);

        /*
         * 如果请求失败则
         * AuthCodeAction action = new AuthCodeAction(codeException)
         */
    }

    public void createLoginAction(String account, String authCode){

        //********此处省略网络请求

        LoginAction action = new LoginAction(new LoginResult());
        /*
         * 如果请求失败则
         * LoginAction action = new LoginAction(loginException)
         */
        Dispatcher.getSingleton().dispatch(action);
    }
}

LoginStore

public class LoginStore extends BaseStore {

    private boolean getAuthCodeSuccess;
    private LoginResult mLoginResult;
    private Exception mException;

    @Override
    public void changeSelfData(BaseAction action) {
        String label = action.getLabel();
        switch (label) {
            case "AUTH_CODE":
                onAuthCodeResponse(action);
                break;
            case "LOGIN":
                onLoginResponse(action);
                break;
            default:
                break;
        }
    }

    private void onLoginResponse(BaseAction action) {
        LoginAction realAction = (LoginAction) action;
        mLoginResult = realAction.getResult();
        mException = realAction.getException();
    }

    private void onAuthCodeResponse(BaseAction action) {
        AuthCodeAction realAction = (AuthCodeAction) action;
        getAuthCodeSuccess = realAction.isSuccess();
        mException = realAction.getException();
    }

    public boolean isGetAuthCodeSuccess() {
        return getAuthCodeSuccess;
    }

    public LoginResult getLoginResult() {
        return mLoginResult;
    }

    @Override
    public Exception getException() {
        return mException;
    }
}

Store中保存了LoginActivity中所有需要的状态,包括获取验证码的结果,以及登录结果。Exception是过程中发生的异常,比如网络请求返回的异常等。

然后我们再看下在MVP模式中这样一个登录逻辑是怎么实现的,再根据Mvp模式下的LoginActivity对比一下Flux,可以看出两个框架的优缺点:

public class MvpLoginActivity extends AppCompatActivity implements View.OnClickListener, LoginView {

    private LoginResult mLoginResult;
    private EditText mEtAccount;
    private EditText mEtAuthCode;
    private boolean getAuthCodeSuccess;
    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        mLoginPresenter = new LoginPresenter(new LoginModel(), this);

        Button btnLogin = findViewById(R.id.btn_login);
        mEtAccount = findViewById(R.id.et_account);
        mEtAuthCode = findViewById(R.id.et_authcode);
        Button btnGetCode = findViewById(R.id.btn_get_code);
        btnLogin.setOnClickListener(this);
        btnGetCode.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_get_code:
                mLoginPresenter.getAuthCode(mEtAccount.getText().toString());
                break;
            case R.id.btn_login:
                mLoginPresenter.login(mEtAccount.getText().toString(), mEtAuthCode.getText().toString());
                break;
            default:
                break;
        }
    }

    @Override
    public void loginSuccess(LoginResult result) {
        //登陆成功
    }

    @Override
    public void loginFailed(Exception e) {
        //登录失败
    }

    @Override
    public void getAuthCodeSuccess(boolean isSuccess) {
        //获取验证码成功
    }

    @Override
    public void getAuthCodeFailed(Exception e) {
        //获取验证码失败
    }
}

在Mvp中View层通过Presenter向Model层获取数据并保存在自身内部,然后再调用自己的更新逻辑。这其中View层除了负责交互以及刷新ui的事情,还负责存储Model返回的数据。
这里我们假设一下后台返回的是一个集合,然后本地需要根据某些条件对这些数据进行过滤并且排序,那我们就必须将数据处理的逻辑也放在View层中,如果这种数据处理逻辑比较多而且复杂的话,那整个View层还是会显得很庞大。

我们看看Mvp模式下涉及到的其他类:

LoginPresenter

public class LoginPresenter {

    private LoginModel mLoginModel;
    private LoginView mLoginView;

    public LoginPresenter(LoginModel model, LoginView view){
        mLoginModel = model;
        mLoginView = view;
    }

    public void login(String account, String authCode){
        mLoginModel.login(account, authCode, new Callback<LoginResult>() {
            @Override
            public void success(LoginResult result) {
                mLoginView.loginSuccess(result);
            }

            @Override
            public void failed(Exception e) {
                mLoginView.loginFailed(e);
            }
        });
    }

    public void getAuthCode(String account){
        mLoginModel.getAuthCode(account, new Callback<Boolean>() {
            @Override
            public void success(Boolean aBoolean) {
                mLoginView.getAuthCodeSuccess(aBoolean);
            }

            @Override
            public void failed(Exception e) {
                mLoginView.getAuthCodeFailed(e);
            }
        });
    }
}

LoginModel

class LoginModel {
    public void login(String account, String authCode, Callback<LoginResult> callback){
        //********此处省略网络请求
        callback.success(new LoginResult());
    }

    public void getAuthCode(String account, Callback<Boolean> callback){
        //********此处省略网络请求
        callback.success(true);
    }
}

LoginView

interface LoginView {

    void loginSuccess(LoginResult result);

    void loginFailed(Exception e);

    void getAuthCodeSuccess(boolean isSuccess);

    void getAuthCodeFailed(Exception e);
}

对比一下可以发现Flux中的类是比Mvp要多一点的,这个就多在了Flux中每一个事件都会有一个新的Action类。但是也是因为这个原因,所以在flux中的逻辑更加清晰。

而且当我们在登陆的时候需要这个数据的不仅是登录界面,其他页面也是需要的,包括我们的一下主界面等,这个时候如果需要通知到我们的主界面更新ui的话就只能通过使用一些架构以外的方式进行,比如说广播或者eventbus, 但是如果使用Flux的话只需要在MainActivity的Store中关注“LOGIN”这个事件id,那么MainActivity就不需要什么其他额外的操作去完成。这也是Flux的优势之一。
flux的劣势我们也说到了,就是会导致类比较多,所以在小项目里面不推荐使用,这会使类的结构变得复杂。但是在大项目中,逻辑普遍会比较复杂,如果我们的view中存放的数据过多,会使得本身View还需要去维护自己内部的数据,使整个类变得庞大,这个时候使用flux就能有效的解决这个问题。

flux框架基础类封装的项目相关地址已上传github,感兴趣的朋友可以看看。

推荐阅读更多精彩内容