Android开发中的MVP模式详解
在Android开发中,我们通常会去将项目分成一个个的模块文件夹,来进行管理维护,有的人是直接按照功能来分模块,这也是最常见的,有的人则会按照一定的设计模式,再结合功能来进行项目模式设计,比如MVP、MVVM这两种目前比较流行的项目设计模式,本文主要讲解MVP模式。
MVC、MVP、MVVM
MVC
对于MVC我想大家应该都不陌生,最典型的MVC就是JSP + servlet + javabean的模式了。
这两张是我从百度中截来的图(原谅我太懒,不想画图),从中很容易看出MVC
的大致流程,用户通过操作View
来发送用户请求;Controller
接收到 用户请求 后,负责决定应该调用哪个Model
来进行处理;然后Model
根据用户请求进行相应的业务逻辑处理,并返回数据;最后Controller
调用相应的视图View
来显示Model
返回的数据。
MVP
首先,我们来看一下上图(没错,又是从百度上抠下来的?_?),View
发送指令给Presenter
,Presenter
获取指令后,调用响应的Model
进行业务逻辑处理,然后返回数据给Presenter
,Presenter
根据Model
返回的数据再来调用相应的View
。
MVVM
看上图(盗图狂魔就是我@_@),在MVVM
中有个ViewModel
,它的作用就是与View
进行双向绑定,当View
或者ViewModel
有一方变动时,另一方也会跟着改变,其实就是观察者模式,同时ViewModel
也会处理一些轻量的业务逻辑,具有和MVP
中的Presenter
的一些类似功能。当用户对View
进行操作时,ViewModel
就会直接收到指令,然后调用Model
处理业务逻辑,当Model
返回数据给ViewModel
时,因为ViewModel
与View
双向绑定的缘故,ViewModel
接收到数据后,View
也会跟着改变,省去了ViewModel
特意调用View
来改变View
的状态这一步骤!有兴趣的同学,可以去体验一下Android Studio
中的databinding
简单感受一下。
为什么用MVP
在Android中,对于Activity并没有明确的说它是属于View还是Controller的范畴,Activity既有View的性质,也具有Controller的性质,所以导致MVC在Android中很难明确分工使用,导致Activity很重。而且MVC中View会与Model直接交互,所以Activity与Model的耦合性很高,当后期维护时,稍有变动,可能Model、Activity、XML都会跟着改变,工作量很大,成本太高。
而MVP与MVC最大的不同之处是,MVP将M与V分隔开来,通过P交互,这样在Android中,就可以明确的把Activity当作View处理,虽然可能还有一点逻辑在其中,但是已经无伤大雅;View和Model不直接交互,当View有变动或者Model有变动时,不会相互影响,有太大变动,,耦合性低,对于后期维护来说,特别是项目越来越庞大时,可以很快的理清项目结构,找到需要修改的地方,大大的缩短了工作量。而且,因为View与Model分离的缘故,Model可以单独进行单元测试。
对于MVVM,其实很多框架中都使用到了它,比如说Vue.js、AngularJs都使用到了这种模式,在Android中也有DataBinding这个原生插件可以使用,来达到双向绑定的作用,但只是使用了DataBinding并不是完全的MVVM模式,个人认为还必须有个中间层类似与Presenter一样的层,来处理一些交互;说实话,在Android中使用了MVVM后,的确大大的提高了开发效率,省去了很多代码,但是如果只是使用纯粹的MVVM,当项目变得庞大后,还是有些吃不消的,各种改动的工作量不是闹着玩的,所以个人认为MVVM只是适合中小型项目,对于大项目还是有点吃紧的。
所以,最后个人认为如果你的项目会越来越庞大,但是又想体验MVVM那种便利,不妨试试MVP+DataBinding,其实这就有点类似于MVPVM模式了,方便快捷,即使项目庞大,改动时也不需要太多重构。
MVPLoader
好了,上面说了那么多,我们还是来点实际的吧,下面是本人在项目中对MVP的处理方式,有不同见解的,欢迎大家提出。
==================================View======================================= 所有的view(Activity、FragmentActivity、Fragment...)都必须实现这个接口 public interface IView { // 此方法是为了当Presenter中需要获取上下文对象时,传递上下文对象,而不是让Presenter直接持有上下 文对象 Activity getSelfActivity(); } 这是Activity的基类: public abstract class BaseActivity<P extends IPresenter> extends Activity implements IView { // Presenter对象 protected P MvpPre; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); MvpPre = bindPresenter(); } // 绑定Presenter protected abstract P bindPresenter(); public <T> T $(int resId) { return (T) findViewById(resId); } public <T> T $(int resId, View parent) { return (T) parent.findViewById(resId); } @Override public Activity getSelfActivity() { return this; } @Override protected void onDestroy() { super.onDestroy(); /** * 在生命周期结束时,将presenter与view之间的联系断开,防止出现内存泄露 */ if (MvpPre != null) { MvpPre.detachView(); } } } ==================================Presenter======================================= public interface IPresenter { void detachView(); } Presenter的基类: public abstract class BasePresenter<V extends IView> implements IPresenter { // 此处使用弱引用是因为,有时Activity关闭不一定会走onDestroy,所以这时使用弱引用可以及时回收IView protected Reference<V> MvpRef; public BasePresenter(V view) { attachView(view); } private void attachView(V view) { MvpRef = new WeakReference<V>(view); } protected V getView() { if (MvpRef != null) { return MvpRef.get(); } return null; } /** * 主要用于判断IView的生命周期是否结束,防止出现内存泄露状况 * * @return */ protected boolean isViewAttach() { return MvpRef != null && MvpRef.get() != null; } /** * Activity生命周期结束时,Presenter也清除IView对象,不在持有 */ @Override public void detachView() { if (MvpRef != null) { MvpRef.clear(); MvpRef = null; } } } ===================================demo======================================== 接口: /** * 创建一个类作为纽带,将view、presenter、model的接口方法都串联在一起,更加便于管理 */ public final class MainContacts { public interface IMain extends IView { void showTips(boolean isSucceess); } public interface IMainPre extends IPresenter { void login(String username, String password); } public interface IMainLgc { boolean login(String username, String password); } } Model部分: public class MainLogic implements MainContacts.IMainLgc { public boolean login(String username, String password) { if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { return false; } return true; } } View部分: public class MainActivity extends BaseActivity<MainPresnter> implements MainContacts.IMain { private EditText editT_username, editT_password; private Button btn_login; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); addListeners(); } @Override protected MainPresnter bindPresenter() { return new MainPresnter(this); } private void initUI() { editT_username = $(R.id.editT_username); editT_password = $(R.id.editT_password); btn_login = $(R.id.btn_login); } private void addListeners() { btn_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MvpPre.login(editT_username.getText().toString(), editT_password.getText().toString()); } }); } @Override public void showTips(boolean isSucceess) { if (isSucceess) { Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "登录失败!", Toast.LENGTH_SHORT).show(); } } } Presenter部分: public class MainPresnter extends BasePresenter<MainContacts.IMain> implements MainContacts.IMainPre { private MainLogic mMainLogic; public MainPresnter(MainContacts.IMain view) { super(view); this.mMainLogic = new MainLogic(); } @Override public void login(String username, String password) { // 判断activity的生命周期是否结束,不判断的话在极端情况下可能会出现内存泄露 if (isViewAttach()) { MvpRef.get().showTips(mMainLogic.login(username, password)); } } }
好了,以上便是本人对MVP模式的一些理解,如果你不想自己在重新搭建MVP框架,可以直接使用MVPLoader项目做依赖:
Step 1: allprojects { repositories { ... maven { url 'https://jitpack.io' } } } Step 2: dependencies { compile 'com.github.albert-lii:MVPLoader:1.0.6' }
如果你有不同的理解,也欢迎提出,大家一起进步。
Github地址
https://github.com/albert-lii/MVPLoader
如果觉得不错,给个赞吧
作者:albertlii
链接:https://www.jianshu.com/p/479aca31d993
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库