组件化的优势:
- 代码架构更加清晰,降低项目的维护难度;
- 组件模式下可以加快编译速度,提高开发效率;
- 项目比较大的情况下,多团队独立开发不同模块,互不影响;
- 利于向插件化变更。
架构:
- App壳工程与具体的业务无关,作为门面封装入口。
- Module实现具体的模块功能,Module之间解耦合,组件可独立于App壳工程单独运行。
- Common用于存放公用资源,例如res、工具类,公用第三方library等,各Module依赖于Common。
Demo
https://github.com/huangyu/ComponentDemo
组件模式和集成模式
gradle.properties中配置isModule=false,通过if(isModule.toBoolean())判断是否是组件模式。
- 注意:每次更改“isModule”的值后,需要点击 “Sync Project” 按钮。
在组件的build.gradle中,区分当前Module是组件模式还是集成模式。
1 | if (isModule.toBoolean()) { |
AndroidManifest和Application文件问题
1 | sourceSets { |
每个使用两份AndroidManifest.xml:
一个用于集成模式,即定义内部组件、权限等信息,不包含Application信息和启动类信息。
一个用于组件模式,包含Application信息和启动类信息,可用于组件单独运行。
组件的Application放置于java/library目录,基于Common库的BaseApplication,集成模式打包不加入。
业务组件自己的 Application 可以在组件开发模式下初始化一些数据,Common 组件初始化公用数据,例如全局Context等。
资源冲突
资源重名,比如App壳工程有资源和Module重名,则会覆盖Module同名资源,造成显示不准确。
利用resourcePrefix “prefix_”在资源名加前缀可避免重名问题。
设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
library 重复依赖
不同Module都依赖了Common,会不会导致 library 重复依赖呢?
实际上在 release 构建 APP 的过程中 Gradle 会自动将重复的 aar 包排除,因此不存在此问题。
混淆问题
一般对于开源的第三方库,没有加入混淆的必要;对于部分闭源的项目,如果需要可用consumerProguardFiles对核心代码和算法进行混淆。
我们自己的项目使用,Module间没有混淆代码的必要(不同开发小组没有必要相互隐藏代码实现吧),统一在App里混淆即可。
组件通信
组件间通信包括三个场景:(1)UI 跳转;(2)调用组件某个类的某个方法; (3)事件通知
前两个场景建议使用强大的路由库:https://github.com/alibaba/ARouter
UI跳转
使用方式:
- 通过@Route定义Activity或者Fragment的路径
- 使用ARouter.getInstance().build(“url”).navigation()进行跳转
如果模块没有集成进来,想要跳转到这个页面的时候,不会崩溃,设置debug模式的时候会出现找不到的提示,而点击属于这个模块的功能的时候则不会有反应。
在没有ARouter或者其他类似的路由库的时候,我们想从组件间进行Activity跳转,怎么处理?
答案就是:反射!只要知道对应要跳转的Activity包名全路径,反射即可获取对应的Activity类,即可进行跳转。
那么我们可以构造一个全局的Map,Map的key是路径,value是具体的类信息,即可实现全局的管理。
另外,需要了解APT:APT技术可以让我们通过自定义注解动态生成编译后的class代码,ARoute里使用了APT去生成注解处理文件。
ARouter背后的原理是怎么样的呢?实际上它的核心思想跟上面讲解的是一样的。
1.在Activity定义@Route注解,会在编译时期通过APT生成一些存储path和activityClass映射关系的类文件。
2.ARoute.init()在app进程启动的时候会拿到这些类文件,把保存这些映射关系的数据保存在map里。
3.通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class(activity.class = map.get(path)),然后new Intent()
4.当调用ARouter的withString()等方法它的内部会调用intent.putExtra(String name, String value)存放传递参数。
5.调用navigation()方法,它的内部会调用startActivity(intent)进行跳转,这样便可以实现两个相互没有依赖的module顺利的启动对方的Activity了。
ARoute的基本思想是这样,当然了ARoute还做了其他很多处理,例如@Interceptor的AOP,@Autowired的依赖注入等,不详细赘述。
调用组件某个类某个方法
这里参考ARoute的README文档:
通过依赖注入解耦:服务管理(一) 暴露服务
1 | // 声明接口,其他组件通过接口来调用服务 |
通过依赖注入解耦:服务管理(二) 发现服务
1 | public class Test { |
事件通知:
第三个场景可使用Android的本地广播或者EventBus:https://github.com/greenrobot/EventBus
定义事件:
1 | public static class MessageEvent { /* Additional fields if needed */ } |
事件响应:
1 | @Subscribe(threadMode = ThreadMode.MAIN) |
注册EventBus:
1 | @Override |
发送事件:
1 | EventBus.getDefault().post(new MessageEvent()); |