Android框架篇—— 从零开始搭建一个完善的MVP开发框架(五),通过组件化开发优化项目的结构

原创声明: 该文章为原创文章,未经博主同意严禁转载。

摘要: 在第三篇文章有位朋友留言说:如果接口的数量有一百个,那么是不是需要写一百个Presenter?答案是不一定的,因为这个问题需要根据实际的业务需求来解决。但是这种一个接口对应一个Presenter的方式能够对项目进行最大限度的解耦,我们能够很方便的复用这个接口。

采用MVP模式引发的一些思考

笔者在研究MVP模式的时候查阅过相当多的资料,其中有两句话令我相当的深刻。一是:使用MVP模式虽然代码量会大大增加,但是为了降低耦合和逻辑上的简洁这个牺牲是值得的。二是:有些公司是根据代码量来统计工资的,所以使用MVP模式对于那些用代码量来统计工资的开发者来说,这应该是一个优点(然而,我们公司的工资和代码量时无关)。我当初就是信了你们的邪,当项目达到一定的程度的时候如果采用网上推荐的方式,重复的代码多到你无法想象好吗?每个Presenter都要处理大量重复的逻辑,项目中存在无数个功能相同的Model(向服务器发起请求和发送回调数据到Presenter)例如进度条的显示等等。不过当笔者意识到这个问题后,就通过泛型和封装优化了代码,解决了上述的问题,所以就有了前面的四篇文章。但是有一个问题是没办法通过优化代码解决的,那就是Presenter的管理问题。

这里笔者说一个例子,在开发的时候我接到一个需求是把应用中的一个模块完全移植到另外一个应用中。这个需求很简单,但是有一个问题就是:由于项目达到一定的体量后Presenter的数量和View接口文件比较多,所以在移动模块的时候需要判断那些文件时必要的,很容易漏掉某个文件或者添加了多余的文件。而且由于数据传递等原因,把模块移动到新框架中还需要重新对项目进行调试一遍又一遍才能运行起来。做完这一切所花费的时间并不短,并且还需要重新对这个模块乃至整个项目都测试一遍。

把一个模块迁移到另外一个应用中其实是很常见的问题。特别是在同一间公司不同应用中相似模块背后的逻辑其实是类似的,只是View的展现有所区别而已(例如登陆模块,登陆模块采取的逻辑与登陆校验其实是类似的)。所以如果能把模块以组件的形式分离出来,当需要开发一个新app的时候,再通过一个app模块把这些模块像拼积木一样拼接起来这样不就会大大增加我们的工作效率了吗?

关于组件化开发,笔者也查阅过很多大牛写的文章,这个概念看起来高尚大,实际上通过gradle可以很简单地实现组件化。采用组件化进行开发还能顺便解决我们管理Presenter的问题。因为当你采用组件化开发的时候,单个组件中的Presenter的数量其实不多的,所以采用组件化开发不用担心管理Presenter的问题(什么,你说10来个Presenter你都无法管理了?我选择死亡。。。)。如果你们某个业务组件的接口有几十个的话,这个就是你们的业务架构有问题了。

什么是组件化开发

组件化开发的核心就是把业务模块封装成一个高内聚,低耦合的组件。所谓的高内聚低耦合的基本要点就是,这个业务组件完全不依赖于其它的业务组件,这个组件的功能是完善的能单独拿出来使用的,并且其它的模块能够轻松唤起这个组件进行工作。

光靠文字可能大家会觉得有点难以理解,下面来看看一个关于电商平台的组件化简图

电商平台组件化简图

这个APP有4个组件,这四个组件分别依赖于BaseLib组件,而且各个组件都是单独存在没有任何交集的。BaseLib中包含了我们前面4篇文章所开发的MVP开发框架组件、baseApp组件(这里放style,drawable资源和自定义Application)、工具类组件等。

可能有读者会产生疑问,个人中心和购物车是需要登录后才能使用的功能,但是这两个组件和登陆注册模块之间是没有依赖的。那么怎么样判断App实际有没有登陆呢?在这里笔者是通过Base组件中的自定义Applcation实现的。当登陆成功后,把获取到的密钥交给Applcation处理就可以了。

组件化开发有什么优点

  1. 业务组件可以很方便地移植到另外一个App。
  2. 项目的分工可以更加合理,因为不同组件之间是没有依赖关系的。所以可以几个组件同时开发。
  3. 可以实现组件的单独编译和测试。
  4. 项目之间的耦合度更低,更好管理。

道理大家都懂,但是具体要怎么做呢?下面我就来教你们如何实现组件化。

组件化的项目结构

项目的结构

项目结构图

上图中的Business就是我们的业务层代码,其中的test就是我们的一个测试组件。

实现组件化

组件化的关键是通过gradle脚本实现组件间的的属性变化(这里称作为属性是为了方便大家理解,实际上这个是一个插件,这个插件的作用时帮助我们构建项目,这里涉及到gradle自动化构建方面的知识,这里就不深入了)。何谓属性变化呢?不知道大家在平时开发的时候要没有认真看过build.gradle文件,这个文件的第一句代码就确定了这个Module的属性。
一般我们在Android Studio创建一个project的时候,我们的项目中就会带有一个Module,这个Module的名字就叫app。现在我们来看看app中build.gradle的第一句代码是怎样写的:

1
apply plugin: 'com.android.application'

这句代码的作用是规定了我们app这个Module是一个application,意思就是app这个Module的属性是application。除了application属性外我们还需要用的一个属性是library属性,这个属性规定了module是一个library,这两个属性只能同时存在一个。我们在前面用到的volley框架就是一个library。下面我们来看看它的build.gradle是怎么写的:

1
apply plugin: 'com.android.library'

因为我们组件化的要求是,组件必须能够组合一起运行也能单独开发、单独测试所以我们的组件需要能够单独运行才能满足我们的要求。
在Android中Module的属性必须是application才能单独运行起来。所以当我们开发完一个业务组件之后,我们把build.gradle的属性改为application就能单独运行起来了,如果需要组合在一起则需要把属性改为library即可。这里有一点是需要注意的,当Module的属性为application的时候,必须要有一个入口activity,入口activity就是指我们设置了intent-filter为Main的activity。就是下面这个:

1
2
3
4
5
6
7
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

而作为library的时候是不能有入口activity的。

这个就是组件化的具体实现,但是这样会有一个问题就是如果组件的数量过多的话,我们管理起来也是一件麻烦事,想象下你不停地重复上述的操作的情景。所以我们需要把这一个过程交给gradle脚本,实现自动化构建。

使用Gradle实现自动化构建

自动化构建这个听起来很厉害的概念实际上是十分简单的,我们直入主题。我们先开口项目根目录下的gradle.properties文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Turn on debugging
isDebug=true
# Deps for gradle
BUILD_TOOLS_VERSION=25.0.0
COMPILE_SDK_VERSION=25
# SDK versions for the samples
MIN_SDK_VERSION=15
TARGET_SDK_VERSION=25
# Deps for libraries
GSON_VERSION=2.7
FRESCO_VERSION=0.9.0
# App Version
VERSION_CODE=1
VERSION_NAME=1.0.0

在这个文件中,我定义了一些全局变量。这些全局变量的作用是同一所有模块中使用的android sdk的版本和一些库的版本,在这里我们只看isDebug这个变量就可以了。isDebug为true的时候我们的所有业务组件的属性就设置为application属性,为false的时候就设置为library属性即可。至于如何解决Manifest中的入口问题,我们先来看看test组件的项目结构:
test组件接口

就是通过创建两个不同的Manifest文件解决的,这两个文件的区别就是debug中定义了入口activity。如果调用这个模块需要初始数据的话,我们创建一个DebugActivity就可以了。例如电商例子中,我们需要一个可以输入商品号添加商品到购物车的DebugActivity。这里需要注意的是,购物车功能需要登录才能使用,我们使用debug模式的时候需要在自定义Application中提供一个debugKey来让提供校验密钥。

下面我们看看test模块中的build.gradle的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
if (isDebug.toBoolean()){
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
if (isDebug.toBoolean() ){
applicationId "com.example.test"
}
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets{
main{
if (isDebug.toBoolean()){
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
}
dependencies {
compile project(':BaseLibraries:BaseMvpLib')
}

在这里只需要关注两个地方就可以,第一个就是根据isDebug的值来确定test组件的属性。第二个就是在sourceSets中根据isDebug的值来选择Manifest文件。
当test属性为application属性时,app Module是无法依赖test的,所以app的build.gradle文件需要根据isDebug的值来选择是否以来test组件。下面是具体实现逻辑:

1
2
3
4
5
6
7
8
dependencies {
if(isDebug.toBoolean()){
compile project(':BaseLibraries:BaseApp')
}else{
compile project(":Business:test")
}
}

对于第三章中读者提出的问题的几个解决思路

第三章中,读者提出来了一个好问题,这个问题对于我们在使用MVP模式进行开发的时候是无法回避的。相信很多读者在看介绍MVP模式的文章的时候都会看到一个问题就是:当业务代码越来越复杂的时候Presenter会变得越来越臃肿。所以在这里,笔者采用的解决方案就是一个Presenter对应一个接口的方法来解决。
当采用笔者的解决方法的时候,如果同一个页面有多个Api接口的话,我们只需要在Activity / Fragment中实现多个IView接口和实例化多个Presenter就可以了。这样做的优点就是,我们的接口就好像是一块块积木一样,当我们需要使用的时候某个接口的时候,通过Presenter就可以很简单的在Activity中使用。这种方案的缺点是当App的体积到达一定的程度后Presenter的数量会很多。我们可以通过组件化的方式降低这个问题带来的影响。
还有一种方案就是,一个页面对应一个Presenter。这个方案有两个思路,一个是:Model负责控制多个Api接口,一个Api接口对应一个发起请求的方法。然后在Presenter中根据实际接口的情况来创建回调方法。另一种是,一个Presenter中控制多个Model然后在需要的时候使用不同的Model向服务器获取数据即可。这种方法的缺点是代码的耦合度会比较高,当页面中的接口过多的时候,Presenter会非常臃肿,并且无法在其他页面中复用这个Presenter接口。当我们某个页面中需要使用同一个接口的时候,我们必须重新实现一次。
笔者认为,如果我们开发的app体量不是很大的时候,我们可以使用第二种方案进行开发。如果APP体积比较大的时候,通过笔者提供的解决方法再配合组件化的项目构建方案能够应对更复杂的实际场景。所以需要采用何种方案就需要读者自行判断了。

小结

关于介绍MVP框架的文章就到这里结束了,在这里是时候接到读者提出的问题了。
当我们使用MVP模式的时候Presenter文件的数量问题是无法解决的,我们只能通过其它的方法来增强我们对于Presenter的管理。相信读者也看出来了,这个解决方法和编写代码的关系并不大。这个方法的思想是使用组件化的方法来构建我们的项目实现各组件间高内聚低耦合的目的。
在这个篇文章中,我们所写的代码并不多,但是却解决了Presenter管理、模块化开发、单模块测试、降低项目耦合度等问题。所以这里也给读者一些建议,就是在解决问题的时候,我们要跳出我们的常规思维,有时候只需要做一小点改变结果就会大大不同。
项目的代码已经提交到了笔者的git中。地址是https://github.com/DobbyTang/mvp-android-framework