Android 中的 flux 应用实践

2,867 阅读10分钟

一、概述及基本概念

最初接触flux这个框架大概是去年下半年,接触React.js,当时没有深入,一脸蒙逼,啥,是框架还是一种编程思想??? 对于初学者,它是一种框架,然而对于高手来说,它仅仅是一种编程思想,因为,当我最近重拾React.js的时候,GitHub上已经有几十种flux的JavaScript的实现版本了,只能说光阴荏苒,时间飞逝。ps:最近在研究它的升级版redux,但愿下半年能写个redux的Android版,为什么,就是为了弥补这个版本中的一些缺陷…

废话不多说,介绍下一些flux的基础概念: flux最初来源于React.js,是脸书在14年提出的web前端框架。AndroidFlux是Facebook的Flux 架构的Android实现。这种架构可以很好的应用于Android平台,相对于其他的MVC/MVP/MVVM等模式,拥有良好的文档和更具体的设计,比较适合于快速开发实现。 github.com/androidflux…上有它的简单的实现版本。

分享2篇不错的我在学习过程中感受颇多的blog,帮助大家理解:

  1. www.jianshu.com/p/896ce1a8e…
  2. www.jianshu.com/p/5aa9cbde2…

二、实现

实现中使用到的技术

在实现上,我没有采取官方demo中的纯代码实现,为了提高框架的灵活性,我采用了square.github.io/的EventBus的otto,即EventBus的注解版,在网络请求方面结合了Retrofit,采用RxJava实现网络请求,跟上潮流,采用注解的形式指定观察者的回调方法。大致就是这样的,接下来介绍下具体的实现细节。

首先介绍下项目的结构

对应官方的图来介绍下项目结构:

这是官方的flux框架的数据流向的图,在Android中大致的应用场景为:一个具体的Activity或者Fragment中通过一个ActionCreator生成一个Action,通过全局唯一的Dispatcher将一个Action发送给订阅了这个类型Action的Store,这些Store接收到相应的Action之后做相应的处理,改变自身的相应状态,向订阅了相应ChangeEvent的View发送ChangeEvent,View接收到相应的Event之后,改变自身的UI状态,至此一个完整的数据流动结束,当然,这里的View指的是相应的Activity或者Fragment。浅显的认识,可能不到位,大家自行理解。

其中的数据流向:

主要的几个类:

下面是我项目中的目录结构: 这个其实是一个library工程,其中包括了一些常用的类,和今天主题不想关的不做介绍。 重点是flux包下的部分:

  1. 核心部分是dispatcher,是一个全局的Action和ChangeEvent的分发者,充当了订阅发布模式中的发布者的角色。
  2. actions包下包括了自定义的Action的基类,ActionCreator的基类,BaseLoadDataActionCreator和BaseRecycleListActionCreator则是我为了适应一些业务场景实现的基于某些场景的基类。
  3. stores是用于存储相应UI状态以及场景数据的PresentationModel。
  4. annotation中包含2个注解,用于标识订阅相应Action的Store的方法,以及用于标识相应ChangeEvent的View的方法,提高代码的灵活性。
  5. ActionMethodHolder、EventChangeMethodHolder则是用于存储相应订阅者的2个容器。
  6. 当然还有一些配套的业务场景的基类BaseFluxActivity、BaseFluxFragment。

接下来我们按照整个数据流向依次介绍相关类。

先从业务场景开始介绍

BaseFluxFragment与BaseFluxActivity相似:

public abstract class BaseFluxActivity extends AppCompatActivity {
    protected Dispatcher dispatcher;
    private STORE store;
    private CREATER actionCreater;
    

BaseFluxActivity是一个范型基类,子类可以通过传入相应场景的Store和actionCreater来调用相应的方法。

   * 是否启用flux模式   * @return   */
  protected boolean flux(){
      return false;
  }
  

通过继承这个方法,可以在子类中决定是否启用flux。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(flux()) {
        dispatcher = Dispatcher.get(new Bus());
    }
    }

在onCreate中进行实例化dispatcher。

@Override
  protected void onResume() {
      super.onResume();
      if(flux() && store() != null) {
          dispatcher.start();
          dispatcher.register(this);
          dispatcher.register(store());
      }
  }
  

在onResume中进行dispatcher在bus里的注册,store对action的注册,view对event的注册。

@Override
protected void onPause() {
    super.onPause();
    if(flux() && store() != null) {
        dispatcher.stop();
        dispatcher.unregister(this);
        dispatcher.unregister(store());
    }
    }

反注册,自行阅读。

    * store 的订阅者方法,用于store.emitStoreChange的响应    * @param event    */
   @BindEvent   public void onEvent(Store.StoreChangeEvent event){
       updateView(event);
   }
   

此处用到了之前介绍的注解来指定view订阅的event的回调方法,用于更新ui状态。

    * 实例化store    * @return    */
   protected final STORE store(){
       if(store == null){
           store = (STORE) newInstance(getType("com.milk.flux.stores.Store"),new Class[]{Dispatcher.class},dispatcher);
       }
       return store;
   }
       * 实例化actionsCreator    * @return    */
   protected final CREATER actionsCreator(){
       if(actionCreater == null){
           actionCreater = (CREATER) newInstance(getType("com.milk.flux.actions.ActionsCreator"),new Class[]{Dispatcher.class},dispatcher);
       }
       return actionCreater;
   }
   

实例化通过反射实例化store和actionCreator,具体实现细节参考GitHub代码。

总结:通过范型配置store、actionCreator在子类中可以灵活地使用flux,最后可以通过updateView自动更新ui状态。

ActionCreators

上面讲到业务场景的Activity中已经持有了ActionCreator的实例了,那么我们就可以借助ActionCreator去发送一些Action,当然这些Action可以由相应的view的事件触发(比如点击事件),也可以自发生成,比如初始化的时候创建一个网络请求。

首先看下Action的实现:

public class Action {
    private final String type;
    private final HashMap data;
    Action(@NonNull String type, HashMap data) {
        this.type = type;
        this.data = data;
    }
    public static Builder type(@NonNull String type) {
        return new Builder().with(type);
    }
    public String getType() {
        return type;
    }
    public HashMap getData() {
        return data;
    }
    public static class Builder {
        private String type;
        private HashMap data;
        Builder with(String type) {
            if (type == null) {
                throw new IllegalArgumentException("Type may not be null.");
            }
            this.type = type;
            this.data = new HashMap<>();
            return this;
        }
        public Builder bundle(String key, Object value) {
            if (key == null) {
                throw new IllegalArgumentException("Key may not be null.");
            }
            if (value == null) {
                throw new IllegalArgumentException("Value may not be null.");
            }
            data.put(key, value);
            return this;
        }
        public Action build() {
            if (type == null || type.isEmpty()) {
                throw new IllegalArgumentException("At least one key is required.");
            }
            return new Action(type, data);
        }
    }
    }

实现比较简单,只包含type和data,内部通过建造者模式实现,真正的action在dispatcher中实现,在actioncreator中仅仅传递相应的参数,而type则是用于比对相应注册在dispatcher里的store的,用于寻找注册了相应action的store的,当某个store注册了这个action时,actioncreator发送一个action就会出发相应store的方法被调用。

public class ActionsCreator {
    protected final Dispatcher dispatcher;
    public ActionsCreator(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }
    public interface Actions{
        String ACTION_SUCCESS = "action_success";
        String ACTION_FAILED = "action_failed";
    }
    public interface Key{
        String KEY_DATA = "key_data";
        String KEY_ERROR = "key_error";
    }
    }

ActionCreator的实现则比较简单,内部只包含一个dispatcher,用于发送一个action,其中的2个接口Actions和Keys则分别代表了:type和data的keys。

看一个具体的实现比较有说服力: 27

public class BaseLoadDataActionCreator extends ActionsCreator {
    public BaseLoadDataActionCreator(Dispatcher dispatcher) {
        super(dispatcher);
    }
    public void loadData(Observable observable){
        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber() {
                    public void onCompleted() {
                    }
                    public void onError(Throwable e) {
                        dispatcher.dispatch("BaseLoadDataActionCreatorLoadData","error",e.getMessage());
                    }
                    public void onNext(T t) {
                        dispatcher.dispatch("BaseLoadDataActionCreatorLoadData","data",t);
                    }
                });
    }
    }

其中部分RxJava的代码,有高原反应的童鞋请忽略,在此仅表达,当你的动作完成之后便可以通过dispatcher发送相应的action。 略微提及下,此处的范型T是相应的网络请求的返回结果中包含的一个实体,不是重点。 至此,一个action已经到了dispatcher中了。

重点介绍Dispatcher

Dispatcher是这个单项数据流的核心类,负责所有数据的传递。这里用到了3次观察者模式(也可以说订阅发布模式),理解这个设计模式,这是再好不过的例子了。 先说说是哪3对情侣:

  1. dispatcher和bus: dispatcher订阅了bus,也就是说当bus post一个事件的时候,那么必然会触发一个dispatcher的方法执行,被执行的方法就是onEvent,当dispatcher发送一个action或者一个event的时候其实都是通过bus的post()方法来实现的,都调用了onEvent方法,通过判断event所属的类来区分是action还是changeEvent,从而来确定是通知store还是通知相应的view。

     * store可以订阅具体的某个action通过action的type区别 * @param event */
    @Subscribe
    public void onEvent(Object event){
        if(event instanceof Store.StoreChangeEvent){
            for(String key:registeEventClasses.keySet()){
                List holders = registeEventClasses.get(key);
                for(EventChangeMethodHolder holder: holders){
                    holder.call(event);
                }
            }
        }else if(event instanceof Action){
            Action action = (Action) event;
            String type = action.getType();
            for(String key:registeActionClasses.keySet()){
                List holders = registeActionClasses.get(key);
                for(ActionMethodHolder holder: holders){
                    if(type.equals(holder.getmActionName())) {
                        holder.call(action.getData());
                        return;
                    }
                }
            }
        }
        }
    
  2. store和dispatcher: store订阅了dispatcher,store订阅dispatcher的目的则是用来接收actionCreator相应的action。

  3. view和store,这里的view指的其实是activity或者fragemnt,用来接收store发出的相应的changeEvent。 其实核心都是通过dispatcher借助于bus来实现转发,这个实现要感谢eventbus这个消息总线的实现。

dispatcher中包含了2个ConcurrentHashMap用来记录相应的订阅者: 1 2 3


   private ConcurrentHashMap> registeActionClasses = new ConcurrentHashMap<>();
   private ConcurrentHashMap> registeEventClasses = new ConcurrentHashMap<>();
   

其中包含几个方法:

 * 将Context中的store以及changeEvent的订阅者一般是Context本身进行register * @param cls */
public void register(final Object cls)
 *解绑相印的context以及store * @param cls */
 public void unregister(final Object cls)
  * actionsCreator中调用dispatcher.dispatch来分发action,action的type  * 以及 data的key-value 一对一对的,向store分发action,其实是调用onEvent方法,  * 在里面判断是action还是storeChangeEvent  * @param type  * @param data  */
  public void dispatch(String type, Object... data)
       * store中调用dispatcher的emitChange来分发StoreChangeEvent    * @param o    */
   public void emitChange(Store.StoreChangeEvent o) {
       post(o);
   }
   

ActionMethodHolder、EventChangeMethodHolder

这两个类主要是用来在dispatcher中记录订阅者信息的,主要通过反射来获取订阅者方法中的注解信息,并记录下来,在相关事件分发的时候用于判断订阅者信息。 核心代码如下:

 * 传入一个obj,获取所有注解中的actionName,参数类型,并保存起来 * @param object * @return */
public static ActionMethodHolder[] findAllFluxActionMethods(Object object) {
    List result = new ArrayList<>();
    try {
        Class clazz = object.getClass();
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            Annotation annotation = null;
            if ( (annotation = hasActionAnnotation(m)) == null) {
                continue;
            }
            String actionName = ((BindAction)annotation).value();
            Class[] paramTypes = m.getParameterTypes();
            if (paramTypes.length != 1) {
                continue;
            }
            Class param = paramTypes[0];
            result.add(new ActionMethodHolder(object, m, param,actionName));
        }
    } catch (Throwable e) {
        e.printStackTrace();
    }
    return result.toArray(new ActionMethodHolder[0]);
    }

Store

之前action已经通过actionCreator到达dispatcher,那么dispatcher会讲action分发给store。

public class Store {
    protected final Dispatcher dispatcher;
    public Store(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }
    protected void emitStoreChange(StoreChangeEvent event) {
        dispatcher.emitChange(event);
    }
    public class StoreChangeEvent{
        public boolean error;
        public String message;
        public int code;
        public StoreChangeEvent(boolean error, String message) {
            this.code = 0;
            this.error = error;
            this.message = message;
        }
        public StoreChangeEvent(int code,boolean error, String message) {
            this.code = code;
            this.error = error;
            this.message = message;
        }
        public StoreChangeEvent() {
            this.error = false;
        }
    }
    }

上面是store的基类,并没有包含注解,仅仅包含了StoreEvent这个bean,用于传递changeEvent的简单java类和发送changeEvent的方法。

public class BaseLoadDataStore extends Store {
    private T data;
    public BaseLoadDataStore(Dispatcher dispatcher) {
        super(dispatcher);
    }
    public void loadDataComplete(HashMap d){
        if(d.get("error") == null){
            this.data = (T) d.get("data");
            emitStoreChange(new StoreChangeEvent(200,false,"load data complete."));
        }else{
            emitStoreChange(new StoreChangeEvent(200,true, (String) d.get("error")));
        }
    }
    public T getData() {
        return data;
    }
    }

上面这个则是Store的子类,它包含一个具体的注解方法,用来表明它订阅了“BaseLoadDataActionCreatorLoadData”类型的action,那么当actionCreator发送这个类型的action的时候,便会触发store的这个方法,从而传递一个changeEvent事件。

回到BaseFluxActivity

当然其实具体事件是传递到BaseFluxActivity的具体子类的,这里只是表达意思。

 * store 的订阅者方法,用于store.emitStoreChange的响应 * @param event */
@BindEventpublic void onEvent(Store.StoreChangeEvent event){
    updateView(event);
    }
 * UI的更新 * @param event */
 protected  void updateView(Store.StoreChangeEvent event){
}

这样一个完整的数据流就完成了。

三、总结

实现这套框架的目的是为了解决在ui逻辑复杂的场景下出现的数据流向复杂,数据的双向流动,从而导致后续出现bug时难以排查是哪一条数据流动过程中出现的问题,当数据单向流动时,我们总能很快的知道出现问题的地方。 当然一开始我也说了这个框架还存在一些问题,因为我真实的项目中使用的时候出现了为了保持代码一致性而为了框架而框架的现象,可以简单地理解为:必须写一个actionCreator和配套的store,anyWhere。而解决办法则是redux这个新出的改进版,大家有兴趣的可以自行了解~~ Game Over~!!! 最后附上GitHub地址:github.com/TopJohn/And…