自从上一篇文章写完,我就有这篇文章的打算,原因主要有:(1)上一篇文章只能说是基本概念,但是没有经过项目实践,难免很多不尽人意的地方(2)方便团队新成员通过看文章的形式了解flux这种思想,之前没有接触过flux的童鞋可以参考这篇文章用flux搭建实用性非常强的Android开发框架(一)了解下flux的基本概念,flux是一种思想,思想是表现精髓的东西,但是如何通过代码具象出来,不同的人也有不同的思路吧,就好像看《哈姆雷特》,一千个人眼中就有一千个哈姆雷特。。好啦,废话不多说,现在开始说下4个多月flux在项目中是如何应用的吧。本篇文章会分为四个部分说明flux在项目中的应用,分别是:再封装,填坑,拓展,总结。
(1)再封装
首先对比下2.0版本和1.0版本的包结构
flux结构图.jpg左边是2.0版本(再封装版本),右边是1.0版本(原始版本)
现在把我们的目光移到2.0的结构图,看看ui包中的base包里面的BaseFluxActivity类,basexxActivity是一种常用的架构封装基类,通过把一些公共使用的类似功能,例如统计等等封装到基类里面,当然封装flux也是同样的道理😄,在看BaseFluxActivity这个类之前,我们不妨先看看base包里面的另外一个接口类IFluxBaseHelper,代码如下:
public interface IFluxBaseHelper {
/*** evenbus的事件回调,也是页面ui更新管理器*/
void onViewUpdate(Object event);
/*** 被继承的Fragment必须初始化返回Store* @return*/
Store initStore();
}
接口类里面有两个方法,具体的用处注释已经非常清楚了,onViewUpdate方法主要用于集中处理事件的输出,initStore()方法就是返回要返回一个store,你要使用flux就要返回sore这也是理所当然,在此不妨再啰嗦一句,flux在项目实际应用中,Store主要集中处理view的输入,并通过事件总线处理机制例如evenbus或者otto返回给view的onViewUpdate方法集中处理输出,全程单向哈~这也是我喜欢flux的原因:逻辑性强,简单,实用,之前没有接触过flux的童鞋可以参考这篇文章用flux搭建实用性非常强的Android开发框架(一)了解下flux的基本概念,这里不就不再啰嗦。看完IFluxBaseHelper,然后再看BaseFluxActivity。
public abstract class BaseFluxActivity extends AppCompatActivity implements IFluxBaseHelper{
public Dispatcher dispatcher;
private Store store;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dispatcher=Dispatcher.get();
}
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
ButterKnife.bind(this);
}
@Override
protected void onStart() {
super.onStart();
store=initStore();
if(store!=null){
dispatcher.register(this, store);
store.onStart();
}
}
@Override
protected void onStop() {
super.onStop();
if(store!=null){
dispatcher.unregister(this, store);
}
}
@Subscribe
public void onEventMainThread(Object event) {
onViewUpdate(event);
}
}
在BaseFluxActivity中的onstart和onstop分别是store的注册和反注册,然后我们再看看onViewUpdate方法,它其实就等价evenbus的onEventMainThread方法,那么为啥要多写一次呢,这里有两个原因:(1)通过自定义的方法名和注释引导团队成员快速理解该方法在框架中的作用(2)懒得在每个新的页面都要写一次onEventMainThread方法,而且还得加个@Subscribe 也是挺麻烦的><|||。。。onEventMainThread工作在主线程,笼统来说可以简单想象成用于集中处理ui的更新,更准确的描述应该是上文所说的操作输出的集中处理,说了那么久,还是用例子说话吧(*^_^*) 嘻嘻……
现在要实现这样的功能:在屏幕上面有个“刷新”两个字的button,现在要实现点击这个按钮时候进行一个耗时操作,然后把button上面的文字更新成“刷新完毕”,这是典型的子线程耗时操作,主线程更新ui例子。
那么用flux应该怎样实现呢?下面贴一下关键代码
(1)响应
@OnClick(R.id.btn_refresh) void onRefresh() {
btn_refresh.setText("refreshing...");
progressBar.setVisibility(View.VISIBLE);
commonActionCreator.refreshData();
}
响应可以简单想象成普通的操作监听事件,这样比较容易理解。
(2)输入
@Override
public void onAction(Action action) {
if(action instanceof CommonAction){
String type=action.getType();
switch (type){
case CommonAction.TYPE_REFRESH:
new RefreshTask().execute(type);
break;
case CommonAction.TYPE_DELETE:
emitStoreChange(type);
break;
}
}
}
输入用一个onAction方法统一处理各种响应。
(3)输出
@Override
public void onViewUpdate(Object event) {
if(event instanceof Store.StoreChangeEvent){
SecondStore.SecondStoreEvent secondStoreEvent= (SecondStore.SecondStoreEvent) event;
if(secondStoreEvent.getOperationType().equals(CommonAction.TYPE_REFRESH)){
btn_refresh.setText("refresh complete");
progressBar.setVisibility(View.GONE);
}else if(secondStoreEvent.getOperationType().equals(CommonAction.TYPE_DELETE)){
Toast.makeText(this,"Delete..",Toast.LENGTH_SHORT).show();
}
}
}
输出用一个onViewUpdate方法统一处理,最后效果如下:
效果.gif以上便是关键代码,大家主要可以看(2)和(3),输入和输出的处理还是比较清晰的,这样至少有几个好处:首先,等我们过了几个月回来再看代码,我们可以很容易看到之前页面代码里面已经存在什么样的操作,另外,方便新成员接手代码,另外我觉得最重要的是,可以快速让我们回忆起当时我们写过的代码。具体的代码大家可以下载文章末尾的源码自己看看。再封装部分到此为止。。。
(2)填坑
目前发现的坑有两个:内存泄露和“啰嗦”
(1)内存泄露
内存泄露问题主要是出现在1.0版本中的Dispatcher类,当时我是用一个list装起来的,具体代码如下:
private final List<Store> stores = new ArrayList<>();
当时的考虑是当我们打开一个新的页面时,可以实现页面之间的通信,当没有合理释放store的时候就会导致内存泄露了,现在改成了这样。。
private volatile Store currentStore;
另外post方法居然是遍历调用的,这在项目中会导致viewUpdte方法的多次调用,这显然是不合理的。
修改前:
private void post(final Action action) {
for (Store store : stores) {
store.onAction(action);
}
}
修改后:
private void post(final Action action) {
if(currentStore!=null){
currentStore.onAction(action);
}
}
经过在项目实践中我总结到:框架的设计应该在于“基础”,新的功能应该放在“拓展”里面,放入一些不经过时间检验的新功能也许也会引起新的问题,其实道理大家都懂,但是有时候打码兴奋过头了就突然忘记了基本原则‘(>﹏<),
(2)“啰嗦”
怎么说呢,其实这也是当时读者问的一个问题,而我也曾经用文字回答了一下,先看看当时的问题。。
这样真的挺啰嗦.jpg
在实际使用中我会定义一个commonCreator和CommonAction,把一些常用的操作都放到里面去,这样20个类似操作的Activity我其实就用1个commonCreator和CommonAction就解决问题了,举个简单的例子,大家看下图:
demo.jpg虽然我的页面是SecondActivity,但是没有用SecondCreator而是CommonCreator,关于什么是Creator,不懂的童鞋还是看看我的上一篇文章文章用flux搭建实用性非常强的Android开发框架(一),这里就不再重复了。
坑的话,目前只发现上面两个,以后有新发现会和大家继续分享,大家如果有啥问题也可以和我反馈下哈😄,“填坑”部分到此为止。。下面继续说说拓展。
(3)拓展
先看看我对store的一个拓展。
public abstract class CommonListviewStore extends Store{
public abstract BaseAdapter getAdapter();
}
类CommonListviewStore的代码不多,只不过是多了一个抽象方法,但是在项目中的确可以省下不少功夫,有时候就是这样在小的方面不断优化,慢慢就会达到一个量变到质变的效果,我觉得要对自己有点要求才能提高自己的编码水平,这也是拓展优化部分虽然内容很少,但是我会把它放到一个很重要的部分的原因。
(4)总结
在把flux引入项目之前我也参考过一些优秀app的框架设计,但根据实际出发最终还是选择了flux,目前经过4个月左右的版本迭代还没有出现什么大问题,如果大家需要一种逻辑性强,简单,实用的框架,Flux真的是一种不错的选择。最后当然是源码附上code
这里需要说明作者的源码只充当一个抛砖引玉的作用,呼吁大家如果想使用好flux的话,还是要好好自己琢磨下,然后敲出一个真正适合自己的flux哈😄最后如果大家有啥疑惑我也会尽量和大家交流,以后也会写一些比较实用的文章,希望大家多多关注
网友评论
1. View 层对 Dispatcher 应该是不可知的
2. 楼主抽象出 ActionsCreatorFactory 类是为了维护 ActionsCreator 类,问题是 ActionCreator 中维护的 Dispatcher 是单例的, 抽象出的 ActionsCreatorFactory 挺浪费的
3. Action 最好有两个泛型,一个是请求,另一个是响应。按楼主对 Store 的设计,响应存在 Store 中,怎么处理并发问题?当 View 层从 Store 中取数据时可能已经被替换成别的了...
第1点:对的,理论上这样最好,我会继续想想优化的方法,也欢迎你发表封装Dispatcher的看法哈
第2点:当时项目刚开始的时候没有考虑到这个浪费问题,而是考虑如何通过一些设计让成员尽快学会使用,关于这点的优化,我会好好琢磨下。
第3点:Action只是充当信息的媒介,而不会放入输入和输出,另外关于store的并发问题,store在一般情况之下都只会发送最终的处理结果让view更新,目前我还没有遇到过类似的情况,遇到了会根据具体问题具体分析。