MVP项目Android知识Android开发

[译]MVP实践(Android)-Part1:让我们了解这个项

2016-12-17  本文已影响267人  唐先僧

使用MVP, RxJava Dagger2, Retrofit2, Test 以及所有最新的现代方法、库来实现一个Android实例应用。

我在StackOverFlow上发现一些有关MVP使用的问题没有回答,这促使我产生强烈的兴趣来写下这一系列文章,并提供一个样例工程(即我自己的实践经验)。

我从去年开始熟悉MVP,一个tuxedo开发,并开始寻找样例和教程。我花了很长时间来处理、连接这个未知大谜题的不同部分。我可以推荐的最有用的网站是caster.io,它总是充满了新的Android视频教程。

解释*MVP本身看起来有些古怪!因为已经有许多文章解释它是怎么工作的,以及它是怎么分层的等等,但是附带足够的评论(注释)来帮助新手了解这个方法的样例少之又少。

变化是怎么开始的。。。

这一切都从SOLID(面向对象的设计原则)开始,感谢亲爱的Robert C. Martin

维基文章的内容我们知道SOLID代表:

- S (SRP): 单一功能原则(认为对象应该仅具有一种单一功能的概念)

- O (OCP): 开闭原则(认为“软件体应该是对于扩展开放的,但是对于修改封闭的”的概念)

- L (LSP): 里氏替换原则(认为“程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的”的概念)

- I (ISP): 接口隔离原则(认为“多个特定客户端接口要好于一个宽泛用途的接口”的概念)

- D (DIP): 依赖反转原则(认为一个方法应该遵从“依赖于抽象而不是一个实例”的概念。依赖注入是该原则的一种实现方式。)

MVP在一定程度上尝试遵循这5条原则的全部。我将竭尽全力在示例项目中逐一定位这些原则,以使它们更加透明。

通过这篇完美的MVP文章,MVP代表:

Model 就是将在View(用户界面)中展现的数据。
View就是用于展示数据(Model)的的界面,同时将用户的命令(events)传递给Presenter,由Presenter对数据进行操作。view通常持有一个Presenter的引用
Presenter就是一个“中间人”(MVC模式中Controller扮演的角色),它同时持有vew和model的引用。

Model?!!!

请注意“Model”这个词语有误导性
它应该是检索或者操作Model的业务逻辑
比如:如果你有一个数据库,在一个表中存储了User数据,你的view想要展示一个用户列表,那么Presenter应该持有一个数据库业务逻辑(比如一个DAO)的引用,通过这个业务逻辑Presenter可以查询到一个用户列表。

你能够对MVP再多做一点解释吗?

不不不不不不!!通过关键字Google不能翻墙的就Bing),你会发现所有关于这个新方法的理论。(或者至少阅读一下这篇文章)。

这个样例项目是关于什么的?

这个应用是Marvel的人物搜索程序,Marvel.com的一个简单Android客户端。此应用程序由我创建,作为smava GmbH技术团队的技术评估的一部分.

Marvel Android 应用程序截屏Marvel Android 应用程序截屏

这个应用需要搜索人物,展现搜索结果并缓存上一次搜索。

这个项目的实现使用了MVP,包含了一些现代的Android开发理念和第三方库,这些都可以改变你的职业生涯!

在接下来的系列文章的不同部分我将竭尽所能去解释一切,即:Dagger,Retrofit,RxJava和Tests。

这个项目使用了Circleci.comTravis-ci.org来做持续集成(CI),使用了Codecov.io来做代码覆盖测试,还使用了google的Firebase,这一部分你可以自己学习,因为一定程度上这已经脱离了本文的主题。

在开始之前,你可以先阅读该工程的README任务列表文件来多做一些了解。

Okey,告诉我你都了解到了什么:

让我们先来看一下项目的结构:
我个人喜欢整洁的代码,所以我喜欢将项目分成有意义的模块,以便我和整个团队保持更清晰的任务。

Modules:

整个工程包含两个main module 和一个java console sample module:

app module包含MVP中的Android View层,其余两层(ModelPresentation)都放在了core中,core是一个纯粹的java包,编译后可以生成一个jar库。

把代码按照这种方式分成几个module有什么好处?!

模块名称清理:

为了使modules看起来方便且好看,你可以像这样编辑settings.gradle文件:

include ':marvel-app', ':marvel-core', ':marvel-console'
project(':marvel-app').projectDir = new File('app')
project(':marvel-core').projectDir = new File('core-lib')
project(':marvel-console').projectDir = new File('console')

这样会使modules看起来像这样:



当然这也会导致相关APK文件的名称发生变化。

怎样避免不同的module中版本冲突以及冗余?

在你的Project module 中使用gradle的一个特性可以很方便的获取一份整洁的build.gradle文件,同时还能避免版本冲突和冗余问题。

首先,将你所有project的依赖放到一个gradle文件中,比如libraries.gradle:

ext {
    minSdkVersion = 9
    compileSdkVersion = 25
    buildToolsVersion = "25.0.0"

    //Android
    androidSupportVersion = "25.0.0"
    butterknifeVersion = "8.0.1"
    
    /*...*/

    libraries = [
            androidSupport   : "com.android.support:support-v4:${androidSupportVersion}",
            appCompat        : "com.android.support:appcompat-v7:${androidSupportVersion}",
            designSupport    : "com.android.support:design:${androidSupportVersion}",
            
            /*...*/
            
    ]
    
    /*...*/

}

然后将他放到你的project的主build.gradle文件(请注意最后一行):

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
        
        /*...*/

    }
}

apply from: "./libraries.gradle"

最后,在你的app module的build.gradle文件中像一个插件一样使用(注意依赖部分):

apply plugin: 'com.android.application'
/*...*/

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    /*...*/
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':marvel-core')

    testCompile rootProject.ext.testLibraries.junit
    testCompile rootProject.ext.testLibraries.robolectric

    androidTestCompile rootProject.ext.testLibraries.mockito
    compile rootProject.ext.libraries.appCompat
    compile rootProject.ext.libraries.androidSupport
    compile rootProject.ext.libraries.designSupport
    
    /*...*/
}

在你的core module 的build.gradle文件中使用:

apply plugin: 'java'
/*...*/

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile rootProject.ext.libraries.rxjava

    testCompile rootProject.ext.testLibraries.junit
    testCompile rootProject.ext.testLibraries.mockito

    compile rootProject.ext.libraries.retrofit
    
    /*...*/
}

core module内部发生了什么事情?

core module的文件结构core module的文件结构

参考SOLID依赖性反转原理“应该依赖抽象而不依赖于具体”或引用Novoda的这篇精彩文章“你不应该把一盏灯直接连接到你的房子”!,所有的两个模块之间的链接 (app&core)通过接口实现,并用Dagger连接。

你不应该把一盏灯直接连接到你的房子你不应该把一盏灯直接连接到你的房子

app module内部又发生了什么?

app module的文件结构app module的文件结构

好了,就这么多吧

请从github上clone一份代码并熟悉一下,因为从下一部分我将更多的介绍dagger以及它是怎样连接各个module和各层的不同对象。

我期待您的意见和帮助以便更好的改进这篇文章。

继续下一篇:MVP实践(Android)-Part2:Dagger使用

原文链接:Yet another MVP article — Part 1: Lets get to know the project

上一篇下一篇

猜你喜欢

热点阅读