Android 组件化开发
前言
最近自己学习了一下组件化,然后参考各位大神的一些知识点,然后自己总结了比较简单化的文档,其实组件化还有很多的地方需要去深入学习的,后面自己再去慢慢补充学习。这里有哪里存在不足,可以完善的,希望大家不吝指出,谢谢!
1. 模块化、组件化和插件化的关系
在技术开发领域,模块化是指分拆代码,即当我们的代码特别臃肿的时候,用模块化将代码分而治之、解耦分层。具体到 android 领域,模块化的具体实施方法分为插件化和组件化。
1.1 组件化与模块化相关定义
组件化:指的是单一的功能组件;就是基于可重用的目的,将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件。
模块化:就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容。如:登录功能、注册功能,查询功能都可以是一个模块。模块相对于组件来说粒度更大。
组件化开发:将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk。
插件化开发:将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
1.2 组件化和插件化的区别
- 插件化是在运行时,而组件化是在编译时。
- 插件化可以动态增加和修改线上的模块,组件化的动态能力相对较弱,只能对线上已有模块进行动态的加载和卸载,不能新增和修改。
2. 组件化的实现
2.1 需要实现的六大功能
- 代码解耦
- 组件单独运行
- 数据传递
- UI跳转
- 组件的生命周期
- 集成调试
- 代码隔离
2.2 基本配置
-
新建几个module,用于后面进行组件化操作;然后在gradle.properties文件中加入一句变量配置:
isBuildModule=false
-
使用组件化,容易出现的几个问题:
- module 中 Application 调用的问题
- 跨 module 的 Activity 或 Fragment 跳转问题
- AAR 或 library project 重复依赖
- 资源id冲突
为了解决以上的这些问题,我们需要进行以下的配置。
-
解决不同module 中 Application 调用的问题
在build.gradle文件中第一行,一般都设置一行 apply plugin: ‘com.android.application’这就表示这个module是application模式,可以独立运行的Android程序,也就是我们的APP;
如果设置为apply plugin: ‘com.android.library’,这就表示library属性,不可以独立运行,一般是Android程序依赖的库文件。
在组件化开发过程中,只能运行一个App,如果是”组件开发模式”就将这个组件应用为application模式,如果不是就将这个组件应用为library依赖库。
使用前面配置的参数进行判断。
-
在app主项目依赖其他module时进行判断:
-
在app外的其他module中加入判断:
-
每当我们需要从组件开发模式和APP整体开发模式转换时,只需要修改”isBuildModule”的值即可。
-
除此之外,不同module在不同的运行方式下,其AndroidMainifest.xml也是不相同的,需要为其分别提供自己AndroidManifest.xml文件:在相应的module工程src目录下(其他位置创建)创建两个目录,用来存放不同的AndroidManifest.xml。
这时候,当在组件开发模式下,组件的注册表文件使用debug文件夹下的,其他情况使用release文件夹下的注册表文件。
debug文件夹中注册表的标签中指定了具体application类、属性和要启动的 Activity,而release文件夹中的则没有。
debug文件夹下的AndroidMainifest.xml:
release文件夹下的AndroidMainifest.xml:
-
跨 module 的 Activity 或 Fragment 跳转问题
-
隐式启动
通过设置intent-filter实现,这需要在manifest中插入大量代码,同时也降低了安全性(其他app就可以通过这种方式随意启动)。
-
定义 scheme 跳转
Scheme的方式是建立映射表,集中处理Activity,这种方式可以传递一定的数据。Activity传递大量数据时可以通过EventBus来进行传递(其实即使通过intent显示启动,也不要把大量数据放置在intent中,intent对数据大小有限制)。
-
使用ActivityRouter
通过注解的方式进行,同时支持http和程序内Activity跳转。详细使用在后面会提到。
-
-
AAR 或 library project 重复依赖
在Android Studio中,如果是 aar 依赖,gradle 会自动帮我们找出新版本的库而抛弃旧版本的重复依赖。但是如果你使用的是 project 依赖,gradle 并不会去去重,最后打包就会出现代码中有重复的类了。
解决方法:
一种是将 compile 改为 provided,只在最终的项目中 compile 对应的代码,但是这种办法只能用于没有资源的纯代码工程或者jar包;
另一种在编译时检测依赖的包,已经依赖的不再依赖。
-
资源id冲突
在合并多个组件到主工程中时,可能会出现资源引用冲突, 最简单的方式是通过实现约定资源前缀名(resourcePrefix)来避免,需要在组件的build.gradle脚本中配置:
andorid{ ... buildTypes{ ... } resourcePrefix "modulea_" }
一旦配置resourcePrefix,所有的资源必须以该前缀名开头.比如上面配置了前缀名为modulea,那么所有的资源名都要加上该前缀,如:modulea _tv _content.
3.跨组件跳转库—— ActivityRouter
这里使用的是阿里的ARouter。
Github:https://github.com/alibaba/ARouter
Android平台中对页面、服务提供路由功能的中间件。它通过注解的方式进行,同时支持http和程序内Activity跳转。
目的就是把不同的请求交给不同的控制器,Router作为一个中间层,把页面请求和请求处理进了解耦,而且还可以增加一些自定义功能,在灵活性和扩展性上做一些事情。
-
主要功能(只列出部分,完整可查看github的地址)
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
-
典型应用
- 外部URL映射到内部页面,以及参数传递与解析
- 跨模块页面跳转,模块间解耦
- 拦截跳转过程,处理登陆、埋点等逻辑
- 跨模块API调用,通过控制反转来做组件解耦
接入配置
1、 在app和每个module的build.gradle文件中添加依赖:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
...
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:1.3.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}
2、在app中依赖其他module:
3、在module中的Activity中添加注解:
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
4、 在app自定义的Application中进行ARouter的初始化相关配置。
5、 在app的MainActivity中进行ARouter的跳转设置。
- ARouter传递参数调用with方法,支持多个类型的参数:
- with各种基本类型
- withObject
- withParcelable/withSerializable
- withBundle
- withTransition
注意:
1、在使用withObject传递对象时,使用到了@Autowired注解。
@Autowired是用在JavaBean中的自动装配依赖对象的一种注解。默认按照byType进行注入的,可减少或者消除属性或构造器参数的设置。
在声明相关变量的时候需要和传值的时候key保持一致的名字,并且变量的权限修饰符要使用public。
如:
如果这里使用的是FastJson,那就要注意在bean类中定义无参的构造方法;
但不管是FastJson还是Gson,如果需要传递自定义对象,需要实现 SerializationService,并使用@Route注解标注(方便用户自行选择序列化方式)
不然就会报以下的错误:
2、在低版本转场动画中,navigation()这里要使用上下文,不然动画效果会无法显示。
ARouter.getInstance().build("/test/login").withTransition(R.anim.pre_in,R.anim.pre_out).navigation(this);
完成以上三个配置步骤,基本上就能实现一个简单的组件化开发了。
GitHub代码:
TestComponent
参考文档:
- Android组件化方案
- Android 模块化开发
- Android彻底组件化方案实践
- Android 开发:由模块化到组件化(一)
- Android架构思考(模块化、多进程)