59

Android MVP升级路(一)乞丐版的自我救赎

 6 years ago
source link: http://mp.weixin.qq.com/s/mOdF-PqhqWtZXdHlVRBEaA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android MVP升级路(一)乞丐版的自我救赎

吴七禁 码农职场 2017-11-08 01:34 Posted on

征稿

前段时间有了写一个开源项目的想法。如果有想法的可以一起。缺后台,UI ,当然,也不能少了我们前端,所以有想法的,后台回复“开源”扫码进群。

关于作者

作者:JesseBraveMan

博客地址:http://www.jianshu.com/u/e19dfd08fca0

声明:本文由作者授权发布,未经原作者允许请勿转载

目录

  • 为什么用MVP架构

  • MVP理论知识

  • 乞丐版MVP架构模式的代码实现

  • MVP中的代码复用场景

  • 平民版MVP架构 - base层顶级父类

  • Fragment怎么办

  • 时尚版MVP架构 - Model层的单独优化

引言

记得第一次接触MVP开发是上大学的时候,当时看了数十篇关于MVP的文章,这里不得不吐槽一下国内技术帖子的质量真是参次不齐啊。看完之后一直懵懵懂懂的,总觉有几处关键的地方没搞清但是文章中却一带而过了,比如:

  • 关于如何在Activity中高效的复用Presenter和View;

  • Mode层定义到什么程度才算是比较理想的解耦;

  • Model层与Presenter层如何比较优雅的相互通信。

抱着这些问题,我自己摸索着构建出了一套个性化风格MVP架构,使用过程中也优化了几次,如今一年多过去了再看这套架构也就算是个能用吧,所以决定新的架构优化。

本文讲述了MVP的核心概念和如何从最初的乞丐版MVP架构一步步升级到平民版MVP架构,时尚版MVP架构,以及即将开始更新的旗舰版MVP架构,为了保证思路清晰,文中包含大量代码与文字,跟着文中的例子便可写出一个完整的MVP架构。

为什么用MVP架构

其实我们日常开发中的Activity,Fragment和XML界面就相当于是一个 MVC 的架构模式,Activity中不仅要处理各种 UI 操作还要请求数据以及解析。

这种开发方式的缺点就是业务量大的时候一个Activity 文件分分钟飙到上千行代码,想要改一处业务逻辑光是去找就要费半天劲,而且有点地方逻辑处理是一样的无奈是不同的 Activity 就没办法很好的写成通用方法。

那 MVP 为啥好用呢?

MVP 模式将Activity 中的业务逻辑全部分离出来,让Activity 只做 UI 逻辑的处理,所有跟Android API无关的业务逻辑由 Presenter 层来完成。

将业务处理分离出来后最明显的好处就是管理方便,但是缺点就是增加了代码量。

MVP理论知识

在MVP 架构中跟MVC类似的是同样也分为三层。

Activity 和Fragment 视为View层,负责处理 UI。

Presenter 为业务处理层,既能调用UI逻辑,又能请求数据,该层为纯Java类,不涉及任何Android API。

Model 层中包含着具体的数据请求,数据源。

三层之间调用顺序为view->presenter->model,为了调用安全着想不可反向调用!不可跨级调用!

那Model 层如何反馈给Presenter 层的呢?Presenter 又是如何操控View 层呢?看图!

Image

上图中说明了低层的不会直接给上一层做反馈,而是通过 View 、 Callback 为上级做出了反馈,这样就解决了请求数据与更新界面的异步操作。上图中 View 和 Callback 都是以接口的形式存在的,其中 View 是经典 MVP 架构中定义的,Callback 是我自己加的。

View 中定义了 Activity 的具体操作,主要是些将请求到的数据在界面中更新之类的。

Callback 中定义了请求数据时反馈的各种状态:成功、失败、异常等。

乞丐版MVP架构模式的代码实现

下面我们用 MVP 模式构造一个简易模拟请求网络的小程序。效果图如下:

Image
Image

因为是模拟网络数据请求,所以有三个请求数据的按钮分别对应成功、失败、异常三种不同的反馈状态。

下面是Demo中的Java文件目录:

Image

CallBack接口

Callback 接口是Model层给Presenter层反馈请求信息的传递载体,所以需要在Callback中定义数据请求的各种反馈状态:

Image

Model类

Model 类中定了具体的网络请求操作。为模拟真实的网络请求,利用postDelayed方法模拟耗时操作,通过判断请求参数反馈不同的请求状态:

Image

View接口

View接口是Activity与Presenter层的中间层,它的作用是根据具体业务的需要,为Presenter提供调用Activity中具体UI逻辑操作的方法。

Image

Presenter类

Presenter类是具体的逻辑业务处理类,该类为纯Java类,不包含任何Android API,负责请求数据,并对数据请求的反馈进行处理。

Presenter类的构造方法中有一个View接口的参数,是为了能够通过View接口通知Activity进行更新界面等操作。

Image
Image

xml布局文件

没什么好说的,直接上代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:orientation="vertical"
    tools:context="com.jessewu.mvpdemo.MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:text="点击按钮获取网络数据"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取数据【成功】"
        android:onClick="getData"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取数据【失败】"
        android:onClick="getDataForFailure"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取数据【异常】"
        android:onClick="getDataForError"
        />

</LinearLayout>

Activity

在Activity代码中需要强调的是如果想要调用Presenter就要先实现Presenter需要的对应的View接口。

Image
Image

至此,已经完整的实现了一个简易的MVP架构。

注意!以上代码中还存在很大的问题,不能用到实际开发中,下面会讨论目前存在的问题以及如何优化。

MVP中的代码复用场景

因为上节中乞丐版MVP Demo的代码只实现了一个Activity的请求操作,容易出现一个概念的混淆:

每个Activity都需要有与它对应的一套MVP(Model,View,Presenter)吗?

答案肯定是否定的!

首先不需要数据请求的Activity当然就同样不需要MVP辅助。与其他Activity中存在相同逻辑的Activity,就不需要重复生成对应的MVP。但是这个存在相同逻辑的定义,不同的场景有不同的说法:

Image

场景1:业务逻辑完全相同

场景1中Activity A和Activity C都只有一个“买东西”的逻辑,属于典型的逻辑相同,所以Activity C就可以直接用Activity A写好的MVP无需再做任何处理。

场景2、3:包含部分相同业务逻辑

场景2和场景3的逻辑类似,都属于一个业务逻辑中包含另外一个可以单独存在的业务逻辑,这种情况采用继承的方法即可:

Image

场景4

场景4中Activity C想要同时调用独立服务于Activity A 和 Activity B的业务逻辑,只需要将两个业务逻辑对应的Presenter分别实例化并调用业务方法即可:

Image

不要忘了实现两个Presenter对应的View:

Image

场景5

场景5属于场景3与场景4的结合体,同样需要先把A和B的业务逻辑拆分开,然后同时调用,这里就不举例子了。

总结

通过上面一揽子场景的分析,得出的第一个结论就是MVP的结构太过于繁重,所以为了避免多写重复代码和日后需要进行无意义的修改,在开发前一定要设计好逻辑调用图,这样才能事半功倍。

对于上面经典的通过业务逻辑继承实现包含重复逻辑的方法,其实也可以在一个Presenter中写好完整的逻辑方法,对于不同的Activity需要哪个业务逻辑方法就调用哪个,这样岂不就简单多了。但是从架构设计角度看这种做法是不严谨的,可能存在漏洞,所以为保持软件架构的健壮还是不要偷懒的好。

平民版MVP架构-base层顶级父类

之前说过乞丐版MVP架构模式中还存在很多问题不能应用到实际的开发中,大概存在的问题有:

  • 构架存在漏洞

  • 代码冗余量大

针对这些问题我们需要进一步优化,单车变摩托,升级为可以在实际开发中使用的平民版MVP架构。

调用View可能引发的空指针异常

举一个例子,在上述乞丐版MVP架构中的应用请求网络数据时需要等待后台反馈数据后更新界面,但是在请求过程中当前Activity突然因为某种原因被销毁,Presenter收到后台反馈并调用View接口处理UI逻辑时由于Activity已经被销毁,就会引发空指针异常。

想要避免这种情况的发生就需要每次调用View前都知道宿主Activity的生命状态。

之前是在Presenter的构造方法中得到View接口的引用,现在我们需要修改Presenter引用View接口的方式让View接口与宿主Activity共存亡:

Image
Image
Image

上面Presenter代码中比之前增加了三个方法:

  • attachView() 绑定View引用。

  • detachView 断开View引用。

  • isViewAttached() 判断View引用是否存在。

其中attachView()和detachView()是为Activity准备的,isViewAttached()作用是Presenter内部每次调用View接口中的方法是判断View 的引用是否存在。

把绑定View的方法写到Activity的生命周期中:

Image

结合Activity构建base层

写到这里,相信大多数人都会惊讶于MVP模式代码量的巨大,冗余代码实在太多,所以接下需要为MVP中所有单元都设计一个顶级父类来减少重复的冗余代码。同样的道理,我们也为Activity设计一个父类方便与MVP架构更完美的结合。最后将所有父类单独分到一个base包中供外界继承调用。

CallBack

在乞丐版中Callback接口中的onSuccess()方法需要根据请求数据类型的不同设置为不同类型的参数,所以每当有新的数据类型都需要新建一个Callback,解决方法是引入泛型的概念,用调用者去定义具体想要接收的数据类型:

Image

BaseView

View接口中定义Activity的UI逻辑。因为有很多方法几乎在每个Activity中都会用到,例如显示和隐藏正在加载进度条,显示Toast提示等,索性将这些方法变成通用的:

Image

BasePresenter

Presenter中可共用的代码就是对View引用的方法了,值得注意的是,上面已经定义好了BaseView,所以我们希望Presenter中持有的View都是BaseView的子类,这里同样需要泛型来约束:

Image

BaseActivity

BaseActivity主要是负责实现 BaseView 中通用的UI逻辑方法,如此这些通用的方法就不用每个Activity都要去实现一遍了。

Image

平民版MVP架构代码实现

封装好了base层我们的平民版MVP架构就完成了,下面再来实现一遍之前用乞丐版MVP实现的应用。

Model

Image

View接口

Image

Presenter类

Image
Image

Activity

Image
Image

Fragment怎么办?

日常开发中,并不是所有的UI处理都在Activity中进行,Fragment也是其中很重要的一员,那么如何将Fragment结合到MVP中呢?

实现BaseFragement做法跟BaseActivity很类似,需要注意一下Fragement与宿主Activity的链接情况就可以。

Image
Image

时尚版MVP架构-Model层的单独优化

在从乞丐版MVP架构优化成平民版MVP架构的过程中,几乎每个单元都做了很大优化并封装到了base层,但是唯独Model层没什么变化。所以,时尚版MVP架构的优化主要就是对Model层的优化。

Model层相比其他单元来说比较特殊,因为它们更像一个整体,只是单纯的帮上层拿数据而已。再就是MVP的理念是让业务逻辑互相独立,这就导致每个的网络请求也被独立成了单个Model,不光没必要这么做而且找起来贼麻烦,所以时尚版MVP架构中Model层被整体封装成了庞大且独立单一模块。

优化之后的Model层是一个庞大而且独立的模块,对外提供统一的请求数据方法与请求规则,这样做的好处有很多:

数据请求单独编写,无需配合上层界面测试。
统一管理,修改方便。
实现不同数据源(NetAAPI,cache,database)的无缝切换。
写到这里本片文章实在是太长了,所以时尚版MVP架构的实现就留到下片文章继续。

Android MVP升级路(二)时尚版

未完待续

下篇会完善时尚版MVP架构,以及最新的旗舰版MVP架构设计,敬请期待~

Image

最后

硬广一波最新开发的RecyclerView通用集合适配器SuperAdapter,它的作用是帮助开发者快速构建RecyclerView的Adapter,并封装了许多常用功能,非常好用。之前有人跟我说已经有好多人做过这个了,我想说的是我很感谢之前做过这些的大佬们,是他们给了我灵感和思路,但是不得不承认目前网上的类似项目都存在问题,实际开发中会有些问题,我在前人的思路上进行了大幅度优化,意图打造一个既好用又实用的工具。

目前这个项目还没有全部完成,还有许多实用的功能可以开发,有兴趣的朋友可以一起搞啊。最后,文章链接传送门:RecyclerView多功能集合适配器:SuperAdapter

推荐阅读

仿bilibili刷新按钮的实现

一个精致的打勾小动画

带你实现漂亮的滑动卷尺

Image

分享是一种美德,关注是一种智慧。Image

我就是马云飞

长按识别二维码,关注公众号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK