介绍
上一节我们介绍什么是组件化?接下来我们就实现一个简单的组件化工程,这一节先介绍工程的结构和搭建。
工程的结构
上图是项目的结构图,下面介绍一下几个module的功能:
router_annotation: 定义注解和收集注解内容的数据模型
router_compiler: 注解处理器
route_core: 路由核心,扫描apk中的dex文件,定义组件通信的接口,定义路由分组的接口
base: 将core的接口暴露给上方
module1:业务模块1
module2:业务模块2
app: 主app
组件模式和集成模式的转换
module1和module2都可以是集成模式和组件模式
组件模式是可以独立运行的app,build.gradle引用插件是:
apply plugin: ‘com.android.application’
集成模式是作为aar集成到app中,build.gradle引用插件是:
apply plugin: ‘com.android.library’
我们在开发的时候,当调试的时候module要作为组件模式,当发布app的时候要作为aar引入到app内一起发布,那么怎么优雅的做到两种模式只见轻松的切换呢?
Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否):
# true 组件模式
# false 集成模式
isModule = true
然后我们在module2的build.gradle中读取 isModule,但是 gradle.properties 还有一个重要属性: gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换.
def boolean moduleModel = isModule.toBoolean()
if (moduleModel){
apply plugin: 'com.android.application'
}else apply plugin: 'com.android.library'
组件之间AndroidManifest合并问题
在 AndroidStudio 中每一个组件都会有对应的 AndroidManifest.xml,用于声明需要的权限、Application、Activity、Service、Broadcast等,当项目处于组件模式时,业务组件的 AndroidManifest.xml 应该具有一个 Android APP 所具有的的所有属性,尤其是声明 Application 和要 launch的Activity,但是当项目处于集成模式的时候,每一个业务组件的 AndroidManifest.xml 都要合并到“app壳工程”中,要是每一个业务组件都有自己的 Application 和 launch的Activity,那么合并的时候肯定会冲突,试想一个APP怎么可能会有多个 Application 和 launch 的Activity呢?
但是大家应该注意到这个问题是在组件开发模式和集成开发模式之间转换引起的问题,而在上一节中我们已经解决了组件模式和集成模式转换的问题,另外大家应该都经历过将 Android 项目从 Eclipse 切换到 AndroidStudio 的过程,由于 Android 项目在 Eclipse 和 AndroidStudio开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。
上图是组件化项目中一个标准的业务组件目录结构,首先我们在main文件夹下创建一个module文件夹用于存放组件开发模式下业务组件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 则依然保留,并用于集成开发模式下业务组件的表单;然后我们需要在业务组件的 build.gradle 中指定表单的路径.
sourceSets {
main{
if(moduleModel){
manifest.srcFile 'src/main/module/AndroidManifest.xml'
java.srcDirs 'src/main/module/java','src/main/java'
}else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
全局Context的获取及组件数据初始化
当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成 Application 对象,但是如果我们自定义了 Application,那就需要在 AndroidManifest.xml 中配置我们自己的Applicaton。
但是我们在组件化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取全局的Context,一般都会直接获取 application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。
在 组开始的项目结构图中,功能组件集合中有一个 base组件,所有的module都属依赖整个基础库,所以这个组件中主要封装了项目中需要的基础功能,业务组件就是在 base组件这个地基上搭建起来我们的APP的,所以我们在base组件中封装了项目中用到的各种Base类其中有BaseApplication 类。
BaseApplication 主要用于各个业务组件和app壳工程中声明的 Application 类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了 BaseApplication,当应用启动时 BaseApplication 就会被动实例化,这样从 BaseApplication 获取的 Context 就会生效,也就从根本上解决了我们不能直接从各个组件获取全局 Context 的问题;
这时候大家肯定都会有个疑问?不是说了业务组件不能有自己的 Application 吗,怎么还让他们继承 BaseApplication 呢?其实我前面说的是业务组件不能在集成模式下拥有自己的 Application,但是这不代表业务组件也不能在组件开发模式下拥有自己的Application,**其实业务组件在组件开发模式下必须要有自己的 Application 类,一方面是为了让 BaseApplication 被实例化从而获取 Context,还有一个作用是,业务组件自己的 Application 可以在组件开发模式下初始化一些数据,例如在组件开发模式下,module2没有登录页面也没法登录,因此就无法获取到 Token,这样请求网络就无法成功,因此我们需要在module2这个 APP 启动后就应该已经登录了,这时候组件自己的 Application 类就有了用武之地,我们在组件的 Application的 onCreate 方法中模拟一个登陆接口,在登陆成功后将数据保存到本地,这样就可以处理module2中的数据业务了;另外我们也可以在组件Application中初始化一些第三方库。
但是,实际上业务组件中的Application在最终的集成项目中是没有什么实际作用的,组件自己的 Application 仅限于在组件模式下发挥功能,因此我们需要在将项目从组件模式转换到集成模式后将组件自己的Application剔除出我们的项目;在 AndroidManifest 合并问题小节中介绍了如何在不同开发模式下让 Gradle 识别组件表单的路径,这个方法也同样适用于Java代码;
在业务组件module2的 build.gradle 中,根据 isModule 是否是集成模式将 module/java中的源码继承到清单中:
sourceSets {
main{
if(moduleModel){
manifest.srcFile 'src/main/module/AndroidManifest.xml'
java.srcDirs 'src/main/module/java','src/main/java'
}else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
结束语
组件化的组件之间通信和数据传递需要使用ARouter,而ARouter是一个庞大的库,放到下一节讲解。