当前位置: 首页 > 科技观察

Android中的MVP:Presenter层如何系统化?

时间:2023-03-14 09:39:12 科技观察

MVP(ModelViewPresenter)模式是著名的MVC(ModelViewController)的衍生模式,是Android应用中管理表现层的最佳模式之一。这篇文章于2014年4月***发表,自此火爆至今。所以我决定对其进行更新,解决人们心中的大部分疑惑,将代码转换为Kotlin语言形式。从那时起架构模式发生了显着变化,例如MVVMwithArchitectureComponents,但MVP仍然有效并且是一个值得考虑的选项。什么是MVP模式?MVP模式将Presenter层与逻辑层分开,并在这样做时将UI的工作方式与我们在屏幕上的表示方式分离开来。理想情况下,MVP模式可能会使用完全不同且可互换的接口实现相同的逻辑。首先要明确的是,MVP本身并不是一个架构,它只负责表现层。这是一个有争议的说法,所以我想更深入地解释一下。您可能会发现MVP被定义为一种架构模式,因为它可以成为您的应用程序架构的一部分。但你不应该这么想,因为去掉MVP之后,你的架构还是完整的。MVP仅塑造表示层,但如果您想要一个灵活且可扩展的应用程序,其余层仍然需要良好的架构。完整架构堆栈的一个示例可能是CleanArchitecture,但还有许多其他选项。无论如何,在您从未使用过MVP的架构中使用它总是好的。为什么要使用MVP?在Android开发中,我们遇到了一个严重的问题:Activity高度耦合的用户界面和数据访问机制。我们可以找到一些极端的例子,比如CursorAdapter,它混合了属于视图层的Adapter和属于数据访问层的Cursor。为了轻松扩展和维护应用程序,我们需要使用可以相互分离的架构。如果我们不从数据库中获取数据,而是从Web服务器中获取数据,接下来我该怎么做?我们可能不得不重写整个视图层。MVP使视图独立于我们的数据源而存在。我们需要将应用程序分成至少三个不同的层,以便我们可以独立地测试它们。有了MVP,我们可以把大部分业务逻辑处理从Activity中去掉,这样我们就可以在不使用InstrumentationTest的情况下对其进行测试。如何在Android中实现MVP?好吧,这就是它开始分歧的地方。MVP有很多变体,每个人都可以调整模式以满足他们的需求以及他们感觉更舒服的方式。这主要取决于我们委派给Presenter的任务量。应该由View层负责启用或禁用进度条,还是应该由Presenter负责?谁应该决定ActionBar应该做什么?这是艰难决定的开始。我将展示我通常如何处理这种情况,但我希望这篇文章更多的是一个讨论的地方,而不是对MVP应该如何应用的严格限制,因为根本没有“标准”的方法来做到这一点。对于本文,我实现了一个非常简单的示例,您可以在我的Github上找到登录页面和主页面。为简单起见,本文中的代码使用Kotlin实现,但您也可以在存储库中查看使用Java8编写的代码。模型层在一个完全分层架构的应用程序中,这里的模型只是通往领域层或业务逻辑层的大门。如果我们使用UncleBob的clean架构,这里的Model可能是一个实现用例的Interactor。但就本文而言,将模型视为向视图层显示数据的提供者就足够了。如果您检查代码,您会发现我创建了两个具有人为延迟操作的交互器来模拟对服务器的请求。其中一个交互器的结构:classLoginInteractor{...funlogin(username:String,password:String,listener:OnLoginFinishedListener){//Mocklogin.I'mcreatingahandlertodelaytheansweracoupleofsecondspostDelayed(2000){when{username.isEmpty()->listener.onUsernameError()password.isEmpty()->listener.onPasswordError()else->listener.onSuccess()}}}}这是一个接收用户名和密码并进行一些身份验证的简单方法。View层View层通常由一个Activity(或者一个Fragment,一个View,取决于App的结构)组成,其中包含对Presenter的引用。理想情况下,Presenter是通过依赖注入(如Dagger)提供的,但如果你没有使用这样的工具,你也可以直接创建一个Presenter对象。View唯一需要做的就是:当用户动作发生时(比如点击按钮),它调用Presenter中相应的方法。由于View必须独立于Presenter层,所以需要实现一个接口。下面是例子中使用的接口:interfaceLoginView{funshowProgress()funhideProgress()funsetUsernameError()funsetPasswordError()funnavigateToHome()}接口中有一些有效的方法来显示或隐藏进度条,显示错误信息,跳转到下一页等。如上所述,实现这些功能的方法有很多种,但我更愿意列出最简单直观的方法。然后活动可以实现这些方法。这里我给大家展示一些用法,大家可以大概了解一下它的用法:.GONE}overridefunsetUsernameError(){username.error=getString(R.string.username_error)}}但如果你还记得的话,我还告诉过你,View层使用Presenter来通知用户交互。下面是它的用法:()}}privatefunvalidateCredentials(){presenter.validateCredentials(username.text.toString(),password.text.toString())}overridefunonDestroy(){presenter.onDestroy()super.onDestroy()}...}演示者定义作为Activity的属性,当单击按钮时,它会调用validateCredentials()方法,该方法会通知Presenter。onDestroy()方法也是如此。我们稍后会看到为什么在这种情况下需要通知Presenter。Presenter层Presenter充当View层和Model层之间的中间人。它从模型层获取收据并将格式化的数据返回给视图层。此外,与典型的MVC模式不同,Presenter决定了您在与View层交互时如何响应。因此,它将为用户可以执行的每个操作提供一个方法。我们在View层看到了,这里是代码实现:(username,password,this)}...}MVP模式存在一些风险。一个最重要但也经常被我们忽略的问题就是Presenter总是依附于View。而View层一般都是Activity,也就是说:我们可能会因为长时间运行的任务而泄露Activity。当Activity被销毁时,我们可以更新View。第一,如果你能保证你能及时完成你的后台任务,我不会太担心。泄漏您的活动5-10秒会使您的应用程序变得糟糕,而且解决方案通常很复杂。第二点更令人担忧。假设您花了10秒向服务器发送请求,但用户在5秒后关闭了活动。在调用回调方法和更新UI时,App会崩溃,因为Activity正在被销毁。为了解决这个问题,我们可以在Activity中调用onDestroy()方法,清除View:funonDestroy(){loginView=null}这样就可以避免在任务结束时间和Activity销毁时间不一致时调用Activity。结论在Android中将UI层与逻辑层分离并不容易,但是MVP模式可以更容易地防止我们的Activity最终成为包含数百或数千行代码的高度耦合的类。在大型应用程序开发过程中,需要对代码进行良好的管理。否则,维护和扩展代码将非常困难。如今,还有其他替代方案,如MVVM,我将创建新文章来比较MVVM和MVP并帮助开发人员迁移。所以请继续关注我的博客!