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(); |
最后放一张最终的架构图