用flux搭建实用性非常强的Android开发框架(一)

Android项目做了不少,难免遇到因为在项目架构上设计不合理或者根本没有形成统一的编程思想,导致各种意外的情况出现,最近新项目开始了,笔者就想着上网搜搜其他大神的一些项目架构搭建的资料然后结合自己的经验来搭建一个比较适合自己团队开发的框架,经过一段时间的酝酿,重新看了下mvc,mvp,mvvc思想,又了解了clean architecture,flux等等比较具体点的架构最终决定用Flux架构来搭建。

关于选用Flux作为项目框架的原因主要有以下两点:

  1. 逻辑比较清晰,比较容易使用和维护,也方便测试代码。
  2. Facebook出品,笔者还是觉得可以信赖的,嘻嘻~

好了,废话不多说,接下笔者会结合自己写的demo,谈谈自己使用flux的心得体会。
首先我们先看看Flux架构的整体消息流程:

从流程图中,我们可以看到上面有4个元素:

  1. View :就是一个界面可以想象成一个activity或者fragment,Action就是点击了Activity里面事件操作的响应,例如:点击按钮可以触发一个Action,更新数据可以触发一个Action等等。

  2. Action:对应View的各种响应。

  3. Dispatcher:就是Action的处理中心,负责发送所有的Action。

  4. Store:可以把Store理解为Activity(即View)里面对应的数据和逻辑处理的集合,简单理解为一个view就对应一个store,通过这样的方式来实现activity中ui的更新和数据逻辑处理的解耦。当Dispatcher发送Action的时候,Store会捕获对应Action,然后根据Action提供的信息处理相应的逻辑,处理完毕之后Store就会通过自身的回调去触发对应的View中的相应方法来进行ui的更新。

    从流程图可以看出,数据的流动都是单向进行的,这个是Flux框架最大的特点,也是Flux框架简单实用的原因。大家可以想象下,逻辑和数据都在Store更新,View的更新都在Activity的唯一一个响应方法中更新,那么我们调试代码的时候是非常方便的,粑粑再也不用担心忘记在某个地方隐藏未知的view更新操作,因为所有view的更新操作我们只会从Activity的唯一一个地方实现。
    说到这里。。可能大家有点困惑。。还是直接上代码说明吧。。我会先把自己的设计架构和大家讲解下,然后再分别用两个简单的demo说明。。

    先看看代码目录结构。。主要实现上面的4种元素:view,store,action,dispatcher,分别放在不同的包里面。


    Flux的ui架构设计

<p>大家还可以看到在action包里面还有有一个base的包,我会慢慢说明这个包的作用是神马。在这之前我们回到最初的设计思想,用通俗的话来描述我们在代码应该实现怎样的效果,还是举个例子说明:activity里面有增,删,改等等操作,我们要实现在完成不同的操作之后更新对应activity的界面。结合我设计的框架说明如下:
<ol>
<li> Action包:大家可以看到action下面有个base的包,主要介绍下里面的4个类:</li>

(1)Action类:可以看到action类是抽象类,只可以继承,里面有两个变量,type和一个泛型data,其中type表示操作的类型,例如:增,删等等,值不能为空,data表示所带的参数,如:update(xxx,xxxx)操作带上参数是很正常的,因此我把传参用一个data分装起来,data允许为空,因此方法不带参数也是正常的。

public abstract class Action<T extends IActionEntityBuilder> {

        private final String type;

        private final T data;

        public Action(String type, T data) {
                 this.type = type;
                 this.data = data;
          }

      public String getType() {
                return type;
        }

     public T getData() {
             return data;
       }
    }

(2)ActionsCreator类:消息分发处理类,用于发送不同的action,里面包含Dispatcher,就如上文说到的,用Dispatcher来发送action。

   public class ActionsCreator {

   private static ActionsCreator instance;

   final Dispatcher dispatcher;

   public Dispatcher getDispatcher() {
    return dispatcher;
   }

   ActionsCreator(Dispatcher dispatcher) {
         this.dispatcher = dispatcher;
   }

   public static ActionsCreator get(Dispatcher dispatcher) {
       if (instance == null) {
           instance = new ActionsCreator(dispatcher);
       }
    return instance;
   }

   public void sendMessage(Action action) {
       dispatcher.dispatch(action);
   }

   }

(3)ActionsCreatorFactory类:一个抽象类,里面只有一个构造函数,用于获取actionsCreator,其实整个系统通过一个actionsCreator就可以对不同Action进行处理,但是随着view页面的增多,actionsCreator里面的方法就会越来越多,这会造成代码冗余也不利于我们定位检查代码。因此需要用其他类去继承这个工厂类,来实现对应view的不同操作。例如:Activity1对应着Activity1ActionsCreator来实现Activity1中的各种操作。

   public abstract class ActionsCreatorFactory {   

        public  ActionsCreator actionsCreator;    

        public ActionsCreatorFactory(Dispatcher dispatcher){    
              actionsCreator=ActionsCreator.get(dispatcher);    
       }
    }

 public class MainActionsCreator extends ActionsCreatorFactory {    

     public MainActionsCreator(Dispatcher dispatcher){    
              super(dispatcher);    
     }    

     public void setText(String text){  
        actionsCreator.sendMessage(new MainAction.MessageActionEntity().setText(text).buildWithType(MainAction.ACTION_NEW_MESSAGE));    
     }
}

(4)IActionEntityBuilder类:一个接口类,实现类通过建造者模式填充参数,最后返回一个Action。

 public interface IActionEntityBuilder {    

    Action buildWithType(String type);
}

<li> dispatcher包:里面只有一个Dispatcher类,并提供其一个单例,另外还有一个Store类型的list,另外还有三个方法,register,unregister,dispatch:分别表示注册,反注册,发送消息。</li>
<p>

public class Dispatcher {    

  private static Dispatcher instance;   
  private final List<Store> stores = new ArrayList<>();  

  public static Dispatcher get() {        
     if (instance == null) {            
         instance = new Dispatcher();   
     }        
      return instance;    
  }    

 Dispatcher() {}      

  public void register(Context context,final Store store) { 
       if (!stores.contains(store)) {            
            store.register(context);            
            stores.add(store);        
        }    
   }    

   public void unregister(Context context,final Store store) {
        store.unRegister(context);        
         stores.remove(store);    
    }   

    public void dispatch(Action action) {       
         post(action);    
    }   

    private void post(final Action action) {        
           for (Store store : stores) {            
                  store.onAction(action);        
           }    
    }
 }

<li> store包:看到里面有个抽象类Store,里面就包括了一个view的所有处理逻辑和数据,事件总线我这里是用EventBus,也可以用Otto或者Rxbus作为事件总线,具体可以看看里面的说明:</li>
<p>

public abstract class Store {    
    protected Store() {    }    
    public   void register(Context context){ 
         EventBus.getDefault().register(context);    
    }    

   public   void unRegister(Context context){ 
          EventBus.getDefault().unregister(context);    
   }    
  
   /*传入操作类型,然后触发主界面更新 */
  void emitStoreChange(String operationType) {
    EventBus.getDefault().post(changeEvent(operationType));    
  }    

   public abstract StoreChangeEvent changeEvent(String operationType);    

    /*所有逻辑的处理,在实现类中可以简单想象成对应着一个Activity(View)的增删改查的处理 */
   public abstract void onAction(Action action);    

  /** 返回到view中的对象,在activity得到这个对象,通过operationtype来判断响应的操作去更新对应的ui*/
   public class StoreChangeEvent {        
    
     private String operationType;        
     public String getOperationType() {           
         return operationType;       
     }        

     public StoreChangeEvent(String operationType){ 
           this.operationType=operationType;       
     }    
   }
}

<p>说了一大堆,现在我们用两个小demo来说明下这个框架的使用,后面也会附上demo代码,欢迎大家下载。

demo1:实现点击发送按钮就把edittext里面的内容显示到中间的textView上面去,这个demo就是实现响应activity内的操作,如图



新建一个MainActivity,并实现对应的MainAction,MainActionCreator,MainSotore等等。

public class MainAction extends Action<MainAction.MessageActionEntity> { 

  public static final String ACTION_NEW_MESSAGE = "new_message";    

  public MainAction(String type, MessageActionEntity data) { 
      super(type, data);    
  }   

   /*每个操作参数用该类包装起来,用建造者模式来定制传入的参数,要注意type一定不能为空 ,最后返回的就是一个Action了*/
  public static class MessageActionEntity implements IActionEntityBuilder {        
      private String text;    

      public String getText() {            
         return text;       
     }        

     public MessageActionEntity setText(String text) {
           this.text = text;            
           return this;        
     }        

   @Override        
   public Action buildWithType(String type) {            
         return new MainAction(type,this);       
    }    
  }
}

再看看看MainStore

 public class MainStore extends Store {    
   private static MainStore singleton;    
   private Message mMessage = new Message();    

   public MainStore() {        
       super();    
   }    
   public String getMessage() {        
     return mMessage.getMessage();    
   }    

/*对应一个view的所有逻辑处理*/
  @Override    
  public void onAction(Action action) {        
    String operationType=action.getType();        
        switch (operationType) {            
            case MainAction.ACTION_NEW_MESSAGE: 
                  MainAction.MessageActionEntity   messageActionEntity=(MainAction.MessageActionEntity) action.getData();
            /*获取传入的参数值,即Edittext的值*/
             mMessage.setMessage(messageActionEntity.getText()); 
             break;            
         default:        
   }       
        /*触发view事件的回调,用于更新view的ui*/
         emitStoreChange(operationType);    
  }    
    @Override    
   public StoreChangeEvent changeEvent(String operationType) {        
     return new MainStoreEvent(operationType);    
}    

  public class MainStoreEvent extends StoreChangeEvent{
       public MainStoreEvent(String operationType){  
           super(operationType);        
      }    
   }
 }

然后看看MainActionsCreator,MainActivity的所有操作都通过该类执行。点击发送按钮就是触发settext事件。

public class MainActionsCreator extends ActionsCreatorFactory {    
    public MainActionsCreator(Dispatcher dispatcher){  
         super(dispatcher);    
    }    

    public void setText(String text){ 
        actionsCreator.sendMessage(new MainAction.MessageActionEntity().setText(text).buildWithType(MainAction.ACTION_NEW_MESSAGE));    
    }
}

最后看看MainActivity。。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{    
   private EditText vMessageEditor;    
   private Button vMessageButton;    
   private TextView vMessageView;    
   private Dispatcher dispatcher;    
   private MainStore store;    
   MainActionsCreator mainActionsCreator;    

  @Override    
  protected void onCreate(Bundle savedInstanceState) {             
      super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         initDependencies();        
         setupView();    
 }  

  /*必须反注册*/
 @Override    
 protected void onDestroy() {        
   super.onDestroy();        
   dispatcher.unregister(this, store);    
 }    

 private void initDependencies() {        
    dispatcher = Dispatcher.get();
    mainActionsCreator=new MainActionsCreator(dispatcher);
    store = new MainStore(); 
   /*注册store*/       
   dispatcher.register(this, store);    
 }    

 private void setupView() {       
     vMessageEditor = (EditText) findViewById(R.id.message_editor);        
     vMessageView = (TextView) findViewById(R.id.message_view);        
     vMessageButton = (Button) findViewById(R.id.message_button); 
     vMessageButton.setOnClickListener(this);
     findViewById(R.id.btn_next).setOnClickListener(this);    
}    

@Override    
 public void onClick(View view) {       
     int id = view.getId();       
    if (id == R.id.message_button) {            
        if (vMessageEditor.getText() != null) {
            /*操作都是通过mainActionsCreator触发*/
          mainActionsCreator.setText(vMessageEditor.getText().toString());                       
        }       
    }else if(id==R.id.btn_next){            
            Intent intent=new Intent(this,SecondActivity.class); 
           startActivity(intent);        
    }    
 }   

   private void render(MainStore store) { 
       vMessageView.setText(store.getMessage());   
  }   

  /*Store里面ui响应会在此触发*/
   @Subscribe    
  public void onEventMainThread(Object event) {        
       if (event instanceof MainStore.MainStoreEvent) {
                 /*在此判断是主界面的那个操作,并进行响应的ui更新*/
                  if(MainAction.ACTION_NEW_MESSAGE.equals(((MainStore.MainStoreEvent) event).getOperationType())){    
                       render(store);
                  }     
       }else if(event instanceof SecondStore.SecondStoreEvent){
               Toast.makeText(this, "主界面收到消息了", Toast.LENGTH_LONG).show();        
       }    
   }
}

<p>
demo2:实现不同view之间的消息传送,新定义一个SecondActivity如图:点击SecondActivity上的按钮,如果触发消息MainActivity可以收到的话,那么就会弹出一个土司。

不同activity之间的消息通信
实现流程就是定义对应SecondActivity的store,action等等,消息处理可以参考demo1,关键判断代码就看看上面的MainActivity中的onEventMainThread方法。

@Subscribepublic 
  void onEventMainThread(Object event) {    
     if (event instanceof MainStore.MainStoreEvent) { 
           if(MainAction.ACTION_NEW_MESSAGE.equals(((MainStore.MainStoreEvent) event).getOperationType())){            
                render(store);        
            }  
       /*在此过滤是否响应SecondActivity的请求*/  
     }else if(event instanceof SecondStore.SecondStoreEvent){
         Toast.makeText(this, "主界面收到消息了", Toast.LENGTH_LONG).show();    
     }
  }

至此两个demo就讲解完毕,最后通俗总结下使用我搭建的框架实现Flux的流程:

我们要实现在activity(view)中的操作处理,并响应操作进行ui更新,都要实现activity所对应的Action,ActionsCreator,Store,而且都是一一对应的关系。用上面的demo说明就是:MainActivity对应MainAction,MainStore,MainActionCreator,而且都是唯一对应的关系。当MainActivity操作时,就是通过MainActionCreator进行操作消息发送对应MainAction,然后在MainStore处理不同的MainAction操作,最后MainStore调用emitStoreChange(operationType); 方法来通知view的更新,view的更新操作统一在onEventMainThread中处理。

至此文章就全部讲完了,通过实现我实现的Flux架构,相信你的代码逻辑会变得简单,而且也容易找到错误,在新的项目中就会用该框架进行开发,我也会结合实际开发,把遇到的一些问题贴上来和大家分享,也希望大家可以在自己项目中把架构搭好,尽量少点bug哈~

最后附上Flux架构说明的英文和中文链接,当然还有代码链接:
英文链接
中文链接
code
这个demo也有参考中文链接上面的demo来修改的,这样的目的是为了更方便自己的项目使用,非常敬佩在网上开源分享和分析技术的大神们,谨以此文向大神们致敬!

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,191评论 0 17
  • 如今,功能多样,处理数据量大的web项目,都采用分层设计,一般分为表示层,业务逻辑层,数据访问层。页面上我们能看到...
    芋头木头阅读 1,042评论 0 0
  • 有时候我很感谢我的夫家,感谢我的公婆让我每天归家可以吃上热饭,感谢我的丈夫让我感到孤独的时候给我微暖的拥抱,在我3...
    春藤绕树阅读 104评论 0 0