开源项目Philm的MVP架构分析

前言

最近一直在研究ChrisBannes的开源项目Philm,其整体架构是一套MVP的实现,因为自己也确实没有遇到过整个项目利用MVP搭建的架构,看到的更多是一些代码片段,这里就探讨Philm是如何结合Android实际问题来实现一种MVP架构,如有分析不准确的地方,欢迎指出,大家一起探讨。

项目地址:https://github.com/Juneor/philm

Philm项目运行效果图(典型的Material Design风格):

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的更多资料和讨论可以看文章结尾的相关链接

  1. 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跳转,没有业务逻辑操作。

  1. 核心类分析

    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接口

所有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,也是不错的学习资料。

时间: 2024-10-04 07:37:12

开源项目Philm的MVP架构分析的相关文章

Android MVP架构分析

App架构在Android开发者中一直是讨论比较多的一个话题,目前讨论较多的有MVP.MVVM.Clean这三种.google官方对于架构的态度一直是非常开放的,让开发者自主选择组织和架构app的方式,期望能留给开发者更多的灵活性. 由于没有一套权威的架构实现,现在很多App项目中在架构方面都有或多或少的问题.第一种常见问题是没有架构,需求中的一个页面对应项目中的一个activity或一个fragment,所有的界面响应代码.业务逻辑代码.数据请求代码等等都集中在其中.第二种常见的问题是架构实现

最值得关注的10个C开源项目之Webbench源码分析

Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力.Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行. webbench压测的命令: webbench -c 300 -t 10 url 其中:-c  300 表示并发数(可以了理解成客户端), -t   10表示时间(秒) url   想要压测的url 下载链

Android官方MVP架构解读

综述 对于MVP (Model View Presenter)架构是从著名的MVC(Model View Controller)架构演变而来的.而对于Android应用的开发中本身可视为一种MVC架构.通常在开发中将XML文件视为MVC中的View角色,而将Activity则视为MVC中的Controller角色.不过更多情况下在实际应用开发中Activity不能够完全充当Controller,而是Controller和View的合体.于是Activity既要负责视图的显示,又要负责对业务逻辑的

可删除超炫&amp;多种特效的Card视图(改造自cardsui-for-android开源项目),提供DEMO下载

       这里贴几个效果图,我做了一个gif的动态图,但是不知道为什么上传后图片不动,所以只能放在相册里. 如果大家想看动态的图片演示请点击后面的链接gif动态演示图片 实例Demo下载地址在本文最后 简单介绍 这个Demo主要是使用了cardsui-for-android开源项目,并且做了一些优化和改进: 1.自定义card视图 2.添加长按事件,避免误操作 3.长按后可以删除card,并播放选中动画 4.删除后浮现一个悬浮button 5.点击悬浮button可以恢复之前删除的card视

MVP架构在xamarin android中的简单使用

好几个月没写文章了,使用xamarin android也快接近两年,还有一个月职业生涯就到两个年了,从刚出来啥也不会了,到现在回头看这个项目,真jb操蛋(真辛苦了实施的人了,无数次吐槽怎么这么丑),怪自己太年轻了,还好是给指定行业的人使用. 重新学习思考之后,再看自己在项目中的某些实现的代码,的确不尽人意,甚至想骂自己. 项目经常改,改来该去,代码一直增加,一个fragment也没什么功能,接近1000行的代码,用region括起来,开看起来还挺整齐的,找的时候就凉了.究其原因,没有一种模式,所

MVP架构-Android官方MVP项目和响应式MVP-RxJava项目架构分析对比解读

介绍 MVP这个架构一直是Android开发社区讨论的焦点,每个人都有自己的分析理解众说纷纭.直到GitHub上Google官方发布用MVP架构搭建的项目.感觉是时候分析了. MVP架构简介 这不是本文重点,所以摘抄自李江东的博文 MVP架构简介 对于一个应用而言我们需要对它抽象出各个层面,而在MVP架构中它将UI界面和数据进行隔离,所以我们的应用也就分为三个层次. View:对于View层也是视图层,在View层中只负责对数据的展示,提供友好的界面与用户进行交互.在Android开发中通常将A

Android官方MVP架构示例项目解析

前段时间Google在Github推出了一个项目,专门展示Android引用各种各样的MVP架构,算是官方教程了.趁着还新鲜,让我们来抛砖引玉一探究竟,看看在Google眼里什么样算是好的MVP架构. App架构在Android开发者中一直是讨论比较多的一个话题,目前讨论较多的有MVP.MVVM.Clean这三种.google官方对于架构的态度一直是非常开放的,让开发者自主选择组织和架构app的方式,期望能留给开发者更多的灵活性. 由于没有一套权威的架构实现,现在很多App项目中在架构方面都有或

开源项目成熟度分析工具-利用github api获取代码库的信息

1.github api github api是http形式的api,功能还是比较丰富的,博主因为项目的原因主要用到的是提取project信息这项功能,返回的数据是JSON格式. api页:https://developer.github.com/v3/ Options: (H) means HTTP/HTTPS only, (F) means FTP only --anyauth Pick "any" authentication method (H) -a, --append Ap

100offer举办的「寻找实干和坚持的技术力量」开源项目投票排名分析程序

由于100offer举办的「寻找实干和坚持的技术力量」开源项目投票活动没有按照票数排序的功能,所以本文写了个小程序来实现这个功能,代码如下: import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /**