当项目大到一定程度后,公司各个模块的业务相互耦合,维护的时候非常困难,另外项目大到一定程度后,编译速度也是个坑爹的东西,所以这个时候模块化就显得很有必要了。最近公司一直在推进这方面的工作,于是我也从gayhub上fork了一个项目动手实践起来,下面做个简单的记录。
先贴代码,对着代码看更直观:示例代码
1.模块划分
模块化的第一步就是模块划分要明确,否则所谓的模块化无从谈起。由于本项目直接fork自 https://github.com/BaronZ88/ModularizationProject ,所以直接偷了他的模块划分图来:
可以很明显的看到整个模块分为三层,基础组件层,基础业务层,业务层。最底下一层包含一些第三方开源库和公司自己开发的各种底层库,在这一层中基本上通过gradle引入的第三方框架都在这里配置;第二层是基础业务库,所谓的基础业务库就是有很多业务都可能涉及到这些的,比如一些公用的vo,比如登录业务,比如支付系统;最高一层就是我们真正的模块化的各种业务,这一层的任何一个不同模块都应该可以单独作为一个app跑起来,每次开发时应该只需要运行某一个模块,从而避免影响其它业务。在我们的示例代码中,结构就是这样的:
其中App模块就是我们的主模块,运行这个模块保证所有模块都会被当做module引入,然后其它以Module结尾的都可以单独运行起来。另外CommonBusiness和CoreModel是基础业务层的模块,OpenSourceLibrary是最底下一层基础组件层的module。
有了这些基础,模块化才能继续进行下去。
2.配置运行不同的模块
(1)区分两种状态
首先要明确一点,对任意一个业务module来说,它都有两种状态,一种是运行App模块时的module状态,单纯提供依赖,一种是模块单独运行时的可运行状态。
为了区分这两种状态,我们在gradle.properties文件中添加了一个变量isBuildModule,当为true的时候,各个业务模块能够单独运行,当为false时,每个业务module无法单独跑起来只能作为module引入,将整个应用跑起来,此时只有App模块能运行。
(2)配置两种状态下的gradle文件
一个module是否可以单独运行,是由它的gradle文件中的配置决定的,该module如果只是一个library,通常我们这样写:
apply plugin: 'com.android.library'
但如果要让该module能单独跑起来我们要这么写:
apply plugin: 'com.android.application'
综合一下,结合isBuildModule变量,最终我们这么处理:
if (isBuildModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
这样,我们就能够通过修改isBuildModule来配置是否单独运行模块。
(3)提供两套AndroidManifest.xml文件
由于一个module要想单独跑起来,那么我们必须要指定启动的Activity,但是当isBuildModule为false时又不需要,所以我们要准备两套Manifest文件。以NewHouseModule为例:
Manifest文件分为debug模式和release模式,debug模式就是该模块可以单独运行的模式。然后在该module的gradle文件中配置下:
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
这里分别看下两种文件的区别:
debug:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.baronzhang.android.newhouse">
<application
android:allowBackup="true"
android:icon="@mipmap/new_house_ic_launcher"
android:label="@string/new_house_app_name"
android:supportsRtl="true"
android:theme="@style/NewHouseAppTheme">
<activity
android:name="com.baronzhang.android.newhouse.NewHouseMainActivity"
android:label="@string/new_house_label_home_page">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
release:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.baronzhang.android.newhouse">
<application
android:allowBackup="true"
android:supportsRtl="true">
<activity
android:name="com.baronzhang.android.newhouse.NewHouseMainActivity"
tools:replace="label"
android:label="@string/new_house_label_home_page">
</activity>
</application>
</manif
这里差距主要是是否配置了启动Activity。
(4)配置依赖关系
经过上面三部的配置,按道理可以通过修改isBuildModule变量来配置是否让某个模块单独运行了,然后事实不是这样,当isBuildModule为true时,这个时候运行App模块会报错,我们需要修改下App模块下的gradle文件:
if (isBuildModule.toBoolean()) {
compile project(':CoreModel')
compile project(':CommonBusiness')
} else {
compile project(':InstantMessagingModule')
compile project(':NewHouseModule')
compile project(':SecondHouseModule')
}
当运行的是某个模块是,每一个业务module都是一种application,而不是library,是不能以compile project的方式引入的,所以要排除掉,但也同时排除了这些module依赖的一些基础业务库,所以我们要引入进来。这样,配置每个模块单独运行就完成了。
3.配置路由跳转
由于不同的业务相互解耦的非常彻底,然后有些时候必然会出现一个模块要跳往另一个模块,这种时候,系统自带的跳转已经不行了,因为该模块中没有另一个模块的Activity,这里可以考虑通过Scheme跳转,但一来配置麻烦,二来还有安全风险,所以不推荐。这个时候,Android页面路由就该上场了。
原作者自己撸了一个页面路由框架,我等渣渣只能膜拜,然后自己去github上找了下,最终选定了阿里巴巴开源的ARouter,配置简单,上手简单,使用简单。该框架的详细使用教程看这里:https://github.com/alibaba/ARouter 。这一步没有难度,照着文档来就行了,注意一点,凡是使用ARouter的模块,都要在build.gradle文件中加入
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
这里更推荐的是将 compile 'com.alibaba:arouter-api:x.x.x' 放到OpenSourceLibrary模块中去,每个模块只需要配置annotationProcessor即可。
总结
经过以上操作,最简单的模块化就完成了,后面会继续介绍一些稍微麻烦一些的内容:
Android模块化之登录业务处理
Android模块化之ButterKnife和Dagger2的使用
参考文档:
Android 模块化探索与实践