在android项目开发中,随着功能不断迭代,代码量通常也会随之不断增加,维护成本越来越高。
作为开发者,笔者经常会被杂乱的逻辑搞的焦头烂额,不禁思考:什么样的结构能够简化开发,同时又能降低维护成本?
当下开发中比较推崇的是三层架构,典型代表即是MVP。笔者在此将最近对MVP的理解与心得与大家分享一下。
一直以来,笔者对android中MVP模式的理解并没有很透彻,最近看了kymjs的文章《用MVP架构开发Android应用》,有一种醍醐灌顶的感觉,特在此感谢作者。作者的MVP框架也放到了Github上——TheMVP,感兴趣的朋友可以多加关注。
就个人的理解,目前对MVP的分歧,主要在于Activity的归属问题上。大部分人认为Activity属于V层,但也有部分人认为Activity属于P层。
Activity属于V层?
以登陆页面为例,两个Edittext分别获取账号和密码,一个Button用于登陆。Activity首先setContentView,随后添加Button的点击监听,onClick中获取账号和密码进行请求,一气呵成。流程中setContentView,对Button进行监听,均需要对ui元素直接操作。若是复杂页面,则ui元素的直接操作将会更多,Activity作为V层有天然的优势。
Activity作为P层?
Activity中提供了非常多的回调方法,同时作为Context的实现类,各种Manager均可通过Activity来调用。可以说,我们能想到的绝大部分逻辑操作均可以在Activity来完成,Activity也有作为P层的潜质。
既然Activity作为V层、P层均有一定道理,我们又应该如何理解Activity呢?
Activity无所不能
Activity是上帝类,我们在Activity中做任何事情都是可以的。
试想一下,在一个动态布局的页面中,我们可能不需要xml来写布局,所有控件都是代码生成。各种事件也均有Activity来处理,其中也包括了file操作等。一个Activity将MVP的操作都做了,是不是很万能?
我们是否需要使用MV*模式?
仅从笔者的开发经验来看,对MV*模式的使用并不多,大部分情况下采用Activity/Fragment+Model层。
当我们开发一个逻辑并不特别复杂的app时,只要抽象合理,代码应当不难维护,也不必借助MV*模式了。
但随着PM的奇思妙想越来越多,项目越做越大,一个Activity可能几千行,各种逻辑交织,显得异常复杂。这时候我们就可以考虑通过MV*来拯救我们了!
换句话说,采用MV*模式本身也是一种无奈。业务过于复杂,只能通过拆分层次,将复杂的问题分而治之。
Activity在V层和P层哪种更合理?
Activity因为过于全面,放在哪层都可以。不过正因为它无可替代,不管作为哪层,都需要取舍。
作为V层,则应该将非UI操作放到自定义的P层去。自定义P层本身不具备很多功能(譬如onDestroy释放handler等),这些都需要Activity提供接口实现。
作为P层,则需要将UI操作放到自定义的V层去,UI相关的操作由V层处理,P层不做干预。
笔者认为Activity作为P层较为方便,同时试着将Activity作为P层实现了一个比较简单且并不完善的MVP,仅供参考。
思路
M层没有给出,P层是继承于Activity的PresenterAdapter,V层是自定义的ViewAdapter。
P层的业务处理,很多都要反映在V层上,因此我们的Activity持有ViewAdapter。
当页面较为复杂的时候,可能需要做很多UI处理,ViewAdapter提供各种接口给PresenterAdapter操作也是比较麻烦的。我们可以让P、V持有同一个状态集Info,P修改Info,V根据Info对UI进行渲染。
- PresenterAdapter持有IView(主要为ViewAdapter实现类)和BaseInfo的实现类,初始化两者,并将BaseInfo绑定到IView上。
1 /** 2 * Created by puff on 2016/1/1. 3 */ 4 public abstract class PresenterAdapter<T extends IView, U extends BaseInfo> extends AppCompatActivity implements IPresenter<T, U> { 5 6 private T mView; 7 private U mInfo; 8 9 @Override 10 protected void onCreate(Bundle savedInstanceState) { 11 super.onCreate(savedInstanceState); 12 //初始化view和info,并关联 13 mView = initIView(); 14 mInfo = initInfo(); 15 mView.init(getLayoutInflater()); 16 mView.setInfo(mInfo); 17 setContentView(mView.getRootView()); 18 } 19 20 @Override 21 public U getInfo() { 22 return mInfo; 23 } 24 25 @Override 26 public T getView() { 27 return mView; 28 }
- BaseInfo作为状态集,每对V/P均要实现一个。
1 /** 2 * Created by puff on 2016/1/1. 3 */ 4 public class BaseInfo { 5 private boolean shouldOpenDialog; 6 private boolean shouldCloseDialog; 7 private String dialogTitle; 8 private String dialogMessage; 9 10 public boolean isShouldCloseDialog() { 11 return shouldCloseDialog; 12 } 13 14 public void setShouldCloseDialog(boolean shouldCloseDialog) { 15 this.shouldCloseDialog = shouldCloseDialog; 16 } 17 18 public boolean isShouldOpenDialog() { 19 return shouldOpenDialog; 20 } 21 22 public void setShouldOpenDialog(boolean shouldOpenDialog) { 23 this.shouldOpenDialog = shouldOpenDialog; 24 } 25 26 public String getDialogTitle() { 27 return dialogTitle; 28 } 29 30 public void setDialogTitle(String dialogTitle) { 31 this.dialogTitle = dialogTitle; 32 } 33 34 public String getDialogMessage() { 35 return dialogMessage; 36 } 37 38 public void setDialogMessage(String dialogMessage) { 39 this.dialogMessage = dialogMessage; 40 } 41 }
- ViewAdapter实现了IView接口,每次Activity调用refreshView时进行UI的重新渲染
1 /** 2 * Created by puff on 2016/1/1. 3 */ 4 public abstract class ViewAdapter<T extends BaseInfo> implements IView<T> { 5 /** 6 * 根视图,用来进行view操作 7 */ 8 private View mRootView; 9 /** 10 * 状态集T 11 * View,Presenter通过T通信,T更新进行refresh 12 */ 13 private T mInfo; 14 /** 15 * 通用Dialog共用 16 */ 17 private ProgressDialog mDialog; 18 19 @Override 20 public void init(LayoutInflater inflater) { 21 mRootView = initViews(inflater); 22 } 23 24 /** 25 * 实例化视图 26 * 27 * @param inflater 28 * @return 根视图 29 */ 30 protected abstract View initViews(LayoutInflater inflater); 31 32 @Override 33 public View getRootView() { 34 return mRootView; 35 } 36 37 @Override 38 public void refreshView() { 39 if (mInfo == null) { 40 return; 41 } 42 if (mInfo.isShouldOpenDialog()) { 43 showDialog(); 44 mInfo.setShouldOpenDialog(false); 45 } 46 if (mInfo.isShouldCloseDialog()) { 47 hideDialog(); 48 mInfo.setShouldCloseDialog(false); 49 } 50 } 51 //其他操作 52 }
笔者通过这个结构,完成了一个简单的app,能够满足开发的基本需求。
PresenterAdapter只作业务处理,业务处理结果反映在状态集上,基本上都是针对数据操作的。而ViewAdapter则专注于通过状态集进行UI渲染。
特殊情况
包含AdapterView的页面复杂一些,因为大部分情况下我们需要实现BaseAdapter进行AdapterView的数据填充。
BaseAdapter其实和我们提到的ViewAdapter有相似之处,均是View的管理者,通过数据进行View的渲染。所以我们也可以把BaseAdapter的实现类当作View层来处理。只不过状态集中需要有一个List<XX>来供BaseAdapter使用了。
尚存问题
这个思路还是有很多问题
Fragment要如何处理?
Fragment包含了很多类Activity的功能,生命周期也非常复杂,在复杂应用中经常出现一个Activity包含多个Fragment的场景(Activity->ViewPager->Fragment,这种方式就很复杂),Fragment如何处理,笔者还没有想到很好的办法。
ActionBar,DecorView这种天然存在于Activity中的View如何处理?
ActionBar可以通过其他方式实现,DecorView没有想到什么好办法。
setListener放在P层,一般都需要通过R.id,这样就引用了View?
确实存在这个问题,也可以通过V层提供接口来处理,但会麻烦一些。
笔者水平有限,很多问题都没有解决,也仅是提供一种思路,望园友能够不吝赐教,共同进步~