开源项目Philm的MVP架构分析
作者 : lightsky
原文链接前言
最近一直在研究ChrisBannes的开源项目 Philm ,其整体架构是一套MVP的实现,因为自己也确实没有遇到过整个项目利用MVP搭建的架构,看到的更多是一些代码片段,这里就探讨Philm是如何结合Android实际问题来实现一种MVP架构,如有分析不准确的地方,欢迎指出,大家一起探讨。
1.简单谈一谈MVP
在无任何模式下的开发时,Activity与Model层的关系太紧密了,做了所有的操作,不易维护,扩展性较差。比如我们后期的需求可能不是从 数据库获取数据了,而是从网络,又或者有一个版本要对所有的UI进行大改版,(随着MaterialDesign的出现,我觉得这个还是有可能的),如果 所有的逻辑都在Activity中,那么如此臃肿的代码,怎么修改都费劲吧,又或者你要适应多套UI,比如平板,维护起来也很麻烦吧。
1.2 MVP
MVP是MVC的一种衍生,MVP模式中不容许View直接访问Model,这是MVP与MVC最大的不同之处。View中应该只有UI逻辑,捕 捉用户输入以及视图的渲染。这样将其它复杂的逻辑抽离出来放到Presenter中去,这样就出现了MVP。这种模式和传统的软件工程思想一样,降低了耦 合度,模块化,更方便维护。Presenter通常是通过定义好的接口与View进行交互,那么开发的时候,只要写一个测试类去实现该接口即可模拟用户的 各种操作进行测试,而不需要使用自动化测试工具。甚至可以不用再每次在手机上重新运行应用了,测试也更有效率。
简单的说,就是将View中的复杂工作抽取到Presenter中,降低了耦合度,便于维护和测试,也增强了复用性。
在MVP模式里通常包含4个要素
(1) View : 负责绘制UI元素、与用户进行交互(Activity或Fragment);
(2) View interface : View需要实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试
(3) Model : 负责业务Bean的操作。
(4) Presenter : 作为View与Model交互的纽带,承载了大部分的复杂逻辑。
MVP的优点
1、Model与View完全分离,它们通过接口进行交互,便于维护和测试。
2、可以更高效地使用Model,因为所有对Model的操作都在Presenter内部。
3、我们可以将一个Presener用于多个视图,只需要在Presenter中为不同的View定义View Interface即可,具体的View实现自己的View Interface,即可使用Presenter中的Model操作等。
关于MVP的更多资料和讨论可以看文章结尾的相关链接
2. MVP架构的实现
MVP的具体实现是没有标准的,因为一个项目要考虑的因素很多,你可以按照自己的习惯和需求进行具体的实现。下面我们来分析Philm中实现的MVP架构。
2.1 Philm的总体设计
Philm使用了Controller来统一管理Model、View,按照上面的MVP理解以及Controller实际所做的工作,这个 Controller其实相当于上面的Presenter,但这个Controller更加复杂,在Controller内部,直接定义了MVP中 View与Presenter的交互接口Callback,另外该项目中引入了State的概念(具体见下面的介绍),统一管理了Model和业务中所需 的Event,所有的Activity和Fragment的跳转以及TitleBar和Drawer的管理使用了一个Display来实现。
类关系图
基本调用流程图
2.2 核心概念
2.2.1 Controller
控制中心,简单的可认为是MVP中的Presenter,但是其更复杂。统一管理界面状态的初始化和状态清理,为所有的UI进行渲染并添加 CallBack,统一调度业务相关的后台任务线程,订阅State中定义的Event,进行视图的更新。并统一定义了View与Presenter的 View Interface,一个Controller可以为多个View定义View Interface,因此Controller可以被多个View所共用。
2.2.2 State
保存界面使用到的业Bean,定义业务中所用到Event事件。
每一个界面拥有自己的State接口,ApplicationState负责统一实现所有State接口,ApplicationState扮演了UI和后台线程的通信者,实现保存业务Bean的分发和事件的分发。
使用Controller进行populate时,会从state中获取业务Bean,如果State中没有,Controller则会启动后台 线程请求数据,成功获取数据后通过state.set()分发保存到State中,同时会post一个Event通知Controller进行相应的处 理。
State中定义的事件类型
异步请求完成的通知、数据变更、NetWork的状态变化、LoadingProgress的展示与隐藏,这也是State被单独抽离出来的原 因吧,统一存储了Model并定义了各种State,注意State并不对Model做复杂的操作,只是简单的Set和Get,复杂的操作全部由 Controller处理。
2.2.3 Display
统一控制TitleBar、Drawer以及所有Activity和Fragment跳转,没有业务逻辑操作。
3. 核心类分析
3.1 BaseController
3.1.1 核心方法
(1) init()
所有Controller初始化发起的方法,在BasePhilmActivity中通过mMainController.init()发起,所有的Controller由MainCtroller统一控制。
public final void init() { Preconditions.checkState(mInited == false, "Already inited"); mInited = true; onInited(); }
(2) suspend
public final void suspend() { Preconditions.checkState(mInited == true, "Not inited"); onSuspended(); mInited = false; }
3.2 BaseUiController
统一定义管理某个界面的所有的UI,事件,接口。
在View(这里是Activity或Fragment)中使用的时候获取相应的Controller,然后在onResume中调用getController().attachUI(),为View设置CallBack,然后进行populate,在onPause中调用 getController().detachUi(this)清空UI的所有CallBack以及其它状态的清理。
每一个Controller都拥有一个自己的UiCallBacks,以及一个继承自BaseUiController.Ui 的UI。
Controller是可以共用的
在Controller内部也可同时为不同的View定义相应的UICallBack,因为不同的View可能会使用到相同的Model,State等事 件,不同的View只需实现Controller中定义的与自己相关的CallBack即可。当然该UICallBack需要继承已实现 BaseUiController.Ui 的UI,因为最终都会传入到BaseUIController中进行setCallbacks(UC)。
3.2.1 重要成员变量
mUis:用于存储所有的UI
mUnmodifiableUis:mUis的拷贝,但不可修改
UI<UC>接口
所有Controller的子类的UI都需要实现该UI 接口,拥有CallBack的能力,在Controller attachUi的时候会为传入的UI设置CallBack
public interface Ui<UC> { void setCallbacks(UC callbacks); boolean isModal(); }
3.2.2 重要方法
3.2.2.1 attachUi(U ui)
在Activity或者Fragment的onResume中调用,进行添加回调,视图渲染等工作。
final 类型,不可复写,只是为了Controller的实现类进行状态的管理,如果需要更多的操作,可以实现onUiAttached(UI)方法,该方法会在attachUI中调用。
public synchronized final void attachUi(U ui) { Preconditions.checkArgument(ui != null, "ui cannot be null"); Preconditions.checkState(!mUis.contains(ui), "UI is already attached"); mUis.add(ui); ui.setCallbacks(createUiCallbacks(ui)); if (isInited()) { if (!ui.isModal() && !(ui instanceof SubUi)) { updateDisplayTitle(getUiTitle(ui)); } onUiAttached(ui); populateUi(ui); } }
3.2.2.2 detachUI(U ui)
在Activity或者Fragment的onResume中调用,清空CallBack。
final 类型,不可复写,只是为了实现类进行状态的管理,同上如果有更多的操作,可以实现onUiDetached(ui)方法。
public synchronized final void detachUi(U ui) { Preconditions.checkArgument(ui != null, "ui cannot be null"); Preconditions.checkState(mUis.contains(ui), "ui is not attached"); onUiDetached(ui); ui.setCallbacks(null); mUis.remove(ui); }
3.2.2.3 onInited()
各个Controller进行初始化的时候调用,不用明确的调用,因为MainController统一管理了所有的Controller,在BasePhilmActivity的onResume中通过调用MainController.init()方法统一初始化。
protected void onInited() { if (!mUis.isEmpty()) { for (U ui : mUis) { onUiAttached(ui); populateUi(ui); } } }
各个Controller自身的onInited方法做一些事件的注册等简单的初始化。
3.2.2.4 onSuspended()
清理CallBacks并取消所有注册事件 unregisterForEvents。也是由MainController统一在BasePhilmActivity的onPause中管理。
@Override protected void onPause() { mMainController.suspend(); mMainController.setHostCallbacks(null); mMainController.detachDisplay(mDisplay); super.onPause(); }
3.2.2.5 populate(UI)
对View进行渲染。
3.2.2.6 createUiCallbacks(U ui)
abstract类型,用于Controller创建自己的UICallBack,该方法已经在attachUi(U ui)的时候调用ui.setCallbacks(createUiCallbacks(ui)),为所有UI设置自己的UI回调事件。
3.2.2.7 getId(U ui)
为每一个后台线程和Event设置一个ID。当需要渲染的时候,遍历所有的UI,通过findUi找到目标UI,进行更新。
protected int getId(U ui) { return ui.hashCode(); }
3.2.2.8 findUi(final int id)
与getId(U ui)相对应,获取后台线程或者事件的ID。
3.2.2.9 populateUiFromEvent
populateUiFromEvent(BaseState.UiCausedEvent event)当某些数据更新的时候,需要更新视图时,调用该方法,发送一个事件,通知Controller进行UI更新。
3.3 MainController
3.3.1 onInited()
对所有Controller的CallBack和状态进行初始化
@Override protected void onInited() { super.onInited(); mState.registerForEvents(this); mUserController.init(); mMovieController.init(); mAboutController.init(); }
3.3.2 onSuspended
对所有Controller的CallBack和状态进行清理
@Override protected void onSuspended() { mAboutController.suspend(); mUserController.suspend(); mMovieController.suspend(); mDbHelper.close(); mState.unregisterForEvents(this); super.onSuspended(); }
3.4 BasePhilmActivity
整个项目的MainController的初始化的发起:
3.4.1 重要方法
3.3.1.1 onCreate
mMainController = PhilmApplication.from(this).getMainController(); AndroidDisplay mDisplay = new AndroidDisplay(this, mDrawerLayout);
3.4.1.2 onResume
初始化所有的状态
@Override protected void onResume() { super.onResume(); mMainController.attachDisplay(mDisplay); mMainController.setHostCallbacks(this); mMainController.init(); } public void attachDisplay(Display display) { Preconditions.checkNotNull(display, "display is null"); Preconditions.checkState(getDisplay() == null, "we currently have a display"); setDisplay(display); } @Override protected void setDisplay(Display display) { super.setDisplay(display); mMovieController.setDisplay(display); mUserController.setDisplay(display); mAboutController.setDisplay(display); }
3.4.1.5 onPause()
清空所有的状态
@Override protected void onPause() { mMainController.suspend(); mMainController.setHostCallbacks(null); mMainController.detachDisplay(mDisplay); super.onPause(); }
3.5 实现特定Controller的UI的Fragment
3.5.1 onResume
获取到自己的Controller进行初始化
@Override public void onResume() { super.onResume(); getController().attachUi(this); }
3.5.2 onPause
获取到相应的Controller进行状态清理
@Override public void onPause() { saveListViewPosition(); cancelToast(); getController().detachUi(this); super.onPause(); }
总结
Philm中实现的MVP的架构,遵循了MVP的关键原则:将View和Model隔离。因为整个项目是基于该架构的,所以考虑的更全面,比如状 态的统一管理,所有Controller的统一管理,统一的CallBack的管理,使用Otto驱动事件实现组件间的解耦等。当你希望自己的整个项目完 全使用MVP的架构时,Philm的框架无疑是一种非常值得参考的实现,你也可以根据自己的需求进行扩展。另外Philm也使用了最新的 MaterialDesign设计,有一些自定义的View,也是不错的学习资料。
相关文章
-
http://blog.csdn.net/vector_yi/article/details/24719873
-
http://antonioleiva.com/mvp-android/
-
https://github.com/antoniolg/androidmvp
-
http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
-
https://github.com/android10/Android-CleanArchitecture
-
http://magenic.com/BlogArchive/AnMVPPatternforAndroid
-
http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
-
http://blog.csdn.net/xijiaohuangcao/article/details/7925641
-
https://github.com/pedrovgs/EffectiveAndroidUI/