Android 组件化开发
Android 组件化开发
在项目的开发中,业务模块越来越多,代码量越来越多,编译构建的时间也越来越长,尝试将项目进行组件化开发。
所谓组件化,就是将各个业务模块解耦,在开发的时候将每个业务模块当做单独的 application 开发,在开发完毕后打包成 aar 或者以 module 的形式依赖到主 application 中。
首先,在项目根目录下的gradle.properties文件里设置一个全局变量 isDebug
- 为 true ,即
debug模式,每个module为单个application, - 为 false,即
非debug模式,将app主模块编译为 app,并将其他module依赖进app
AndroidManifest
在每个module中的main文件夹下建立两个文件夹,一个是debug,一个是release,
用来存放AndroidManifest文件
- 在 debug 模式下,每个
module都是单独的app,
所以在AndroidManifest中需要添加一个activity作为启动activity
1 | <activity android:name=".MainActivity"> |
- 在 release 模式下,每个
module都是主app的一个依赖,可以不需要启动activity
如果需要从主app中打开这些activity,可以使用隐式调用或者使用路由表去启动(放在后面再说)
build.gradle
在主
app中,不论在 debug 或者 release 模式下都作为application存在,
即apply plugin: ‘com.android.application’
在各个
module中,在 debug 模式下,作为application,在 release 模式下,作为library
1 | if (isDebug.toBoolean()){ |
- AndroidManifest合并的问题
在各个module中,利用sourceSets(在android标签下)中设置每个模式中的AndroidManifest文件
1 | sourceSets { |
Application
我们知道,一个应用在启动后,系统会为其创建一个applicaiton对象,这个application对象的生命周期就等于整个应用的生命周期,
一般我们会在里头定义一个全局的context。
但是,如果在组件化开发中,在debug模式下,每个module是作为application存在的,但当我们打包时,即release模式下,
每个module是作为library存在的,这时如果使用该module里的getApplicationContext()就会报错,因为release模式下
只有一个application
解决方案:
我们在AndroidManifest合并的问题中,在sourceSets里指定AndroidManifest的路径,
我们也可以指定在release模式下排除掉某些文件
1 | sourceSets { |
所以我们可以在debug文件夹里的AndroidManifest文件可以指定该Application,而在release文件夹下的AndroidManifest中不指定Application
但是这种方法也有局限,因为这样的话,我们的module里就没法使用全局的context对象了。
为了解决上面这个问题,我们将 application 定义在最底层的 module 中(取名为 commonSdk),并在最上层的 app 中的 AndroidManifest 中去使用这个 application ,这样所有的 module 就都可以使用这个 application context 对象了。
资源文件
在开发过程中,我们将module作为一个application使用,所以只会使用该module下res下的资源文件,但当在release模式下,
会将整个工程作为一个application,因此所有的资源文件都会被合并到一起,如果module里的有资源文件同名,则会被覆盖,因此有可能
造成错误
为了解决这个问题,我们可以在所有的资源文件的名字上加个前缀,用module区分开来,则不会产生上述问题,
在gradle中,可以用
resourcePrefix “前缀_”
强制在资源文件名字上加上该前缀,否则会编译不通过,但这个方法不会限定drawable里的资源文件,因此在drawable中的资源文件命名需要在开发过程中加以规范
依赖版本
依赖
在Android开发中,我们会使用很多的依赖库,在非组件化的项目中,我们只需要一股脑将依赖全部写在app的build.gradle
文件中,这样我们的项目就可以用这些依赖了。
但是在组件化中,如果我们也按照这种方式,那就有可能造成重复的依赖,如果我们的firstmodule和secondmodule中都需要http请求,则都去依赖某个http请求库,如果是使用compile 'libiray_name:version'这种方式去依赖,gradle会自动帮我们选出最新的版本去依赖,如果两个module中使用的是不同版本的依赖,并且某个新版本中删除了一些api,如果依赖了旧版本的module使用了这些在新版本中被删除的api,那就会报错。
另外,如果我们的module中是使用compile project(':project')这样的依赖,gradle不会帮我们去重,最后打包后代码
里就会有重复的类。
那如何去解决这个问题呢?
在Application一节中,我们使用了一个commonsdk的module,用来给上游的module提供application,在这个module中,我们也可以在这个commonsdk的module中添加上游的module所需要的依赖,比如http请求库(Retrofit),Gson等等,这样在上游的各个module就都能使用这些依赖。而现在我们只需要在commonsdk这个module中去管理我们的依赖,比如添加、删除、升级依赖。这样我们的主app就不需要去重复依赖一些库了,主app只需要依赖commonsdk这个module,而这个module已经提供了需要的依赖。
版本
如何去管理这些依赖的版本呢,可以在主工程目录下定义变量,进行统一管理
在build.gradle中定义一个标签ext
ext{
compileSdkVersion = 25
buildToolsVersion = "25.0.2"
minSdkVersion = 15
targetSdkVersion = 25
supportLibraryVersion = "25.3.1"
espressoCoreVersion = "2.2.2"
junitVersion = "4.12"
retrofitVersion = "2.2.0"
}
然后将各个module的build.gradle里的变量都通过rootProject.ext.xxx取值
如果是dependencies的话,则用$rootProject.version取值
例如
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile("com.android.support.test.espresso:espresso-core:$rootProject.espressoCoreVersion", {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
testCompile "junit:junit:$rootProject.junitVersion"
compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
}
debug 模式下依赖
按照我们上述的想法去做以后,或许还存在一个问题,就是在debug模式时,我们的各个module是作为application存在的,
这个时候如果我们想安装整个项目app,按照我们的这种模式是无法打开我们的各个业务module的,那难道这个问题就没法解决
了吗?
当然不是。
我们可以修改为
release模式,这样就可以将各个module作为library进行依赖,也就可以打开业务模块了
看到这里你们可能会说 你再逗我吗 = =如果我们想在
debug模式下也能安装app的话,并且能够打开各个业务module,那可以在app的dependencies中
依赖各个module的aar文件,这个aar文件可以在各个module的/build/outputs/aar下找到,可以写脚本或者手动将各个module生成的aar复制到app的libs文件夹下,并进行引用。
1 | android{ |
然后在dependencies进行依赖
dependencies {
if(isDebug.toBoolean()){
compile project(':router')
compile project(':commonsdk')
compile(name:'firstmodule-release', ext:'aar')
compile(name:'secondmodule-release', ext:'aar')
}else {
compile project(':firstmodule')
compile project(':secondmodule')
}
}
这样我们就可以在debug模式下也将整个工程运行起来了,当然,这样其实就没有意义了,因为我们组件化的目的就是为了能够
将各个业务module作为application运行,而不是运行整个app,以加快我们的编译速度等,而这样的话就失去了不需要
全部编译的意义了。
多 Product Flavors 模式下的依赖
本地 AAR 的依赖
在组件化的开发中,有时候遇到某个 lib 需要依赖某个本地 aar 文件
则在 dependencies 添加对本地的 aar 依赖,并将 aar 依赖文件放入 libs 目录下
1 | compile(name:'aar_file_name', ext:'aar') |
如果此时有多个 Flavors ,则在 compile 前添加 Flavor 名字,如
1 | flavors1Compile(name: 'aar_file_name', ext: 'aar') |
但如果这个库以同样的方式被依赖的话,会因为找不到这个文件而报错
1 | Error:Failed to resolve: :arr_file_name: Open File |
这时可以在项目的 build.gradle 中配置
1 | allprojects { |
跳转 router
在不同的module中,要实现跨module之间的跳转,
- 可以使用
intent并设置data里的host和scheme,用隐式调用进行跳转。
例如,在AndroidManifest中设置
1 | <activity |
要进行跳转时,用下面的代码进行跳转
1 | Intent intent = new Intent(); |
最后放一张最终的架构图