文章目录
1.什么是组件化,为什么需要组件化
1.1 适用场景
大型项目、多人协作开发,此时就可以采用组件化,减少编译时间,减少多人协作代码提交导致的冲突.
小型项目、独立完成,这种情况下就没有必要采用组件化来进行过度设计.
1.2 所有代码都在app目录下,通过包名进行管理各个模块,存在哪些弊端?
低内聚问题、高耦合、无重用、层次混乱
低内聚问题:
内部间聚集、关联的程度,那么低内聚就是指内部间聚集和关联程度很低.高耦合:
是对模块间关联程度的度量。耦合的强弱取决与模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。高耦合是指模块间存在紧密的关联关系.无重用:
由于没有将组件或者公共基础库单独封装成一个独立的包,所以别的项目无法重用这些组件,需要重新将代码拷贝过去才能使用.层次混乱:
比如早期的项目,所有的代码都在app模块下面,采用包名来进行模块管理,迭代过多或者代码不规范就会导致层次混乱的问题.
1.3 组件化的意义
不相互依赖、可以相互交互、任意组合、高度解耦、自由拆卸、自由组装、重复利用、分层独立化
解释说明:
不相互依赖:
业务组件之间可以做到不相互依赖;公共组件库之间可以做到不相互依赖可以相互交互:
app与业务组件、组件之间都可以配合ARouter
进行页面跳转任意组合:
app工程可以添加和移除依赖的任意业务组件,从而可以达到快速新增和删除某个模块业务.高度解耦:
业务组件之间相互独立,互不依赖;公共组件库之间相互独立,互不依赖自由拆卸:
可以将单个组件打包出apk,单独进行这个模块的测试 (gradle编码控制
)自由组装:
可以将组件集成到app工程中,打包出正式环境apk (gradle编码控制
)重复利用:
公共基础库可以重复利用在各个组件之中,甚至还可以在公司开发的其他项目中使用;组件与组件之间没有直接关系,可以单独拿出来放在别的项目中使用.分层独立化:
app工程、业务组件和公共基础库之间分工明确
组件化的层次结构:
- app工程
- 业务组件化
- 微信举例:微信、通讯录、发现、我.
还可以把某个功能/项目作为一个组件,比如:QQ里的QQ空间,淘宝中的天猫超市.
- 微信举例:微信、通讯录、发现、我.
- 公共基础库
- Base、Utils、Http、ARouter等等
2.集成环境/组件环境自动部署服务
2.1 通过Gradle来控制正式环境和测试环境的部署
gradle文件访问顺序:
- settings.gradle
- project的build.gradle
- 我们可以通过在project目录下创建一个
global
.gradle(标记的名字可以随便取
)文件,这个文件将app壳工程和模块中一些依赖的库给抽离出来,达到版本统一控制的目的.
然后在app工程和模块就引用这个global.gradle文件中定义的变量.
- 我们可以通过在project目录下创建一个
- app工程的build.gradle
- module的build.gradle
2.2 自定义全局gradle使用
这里我们拿上面创建的global.gradle文件使用来举例.
-
在project目录下创建全局控制的global.gradle文件,
global.gradle
文件代码如下:ext { isApplication = false //false:作为module(不能单独运行),true:作为application(可以作为app单独运行) //对app工程及所有组件版本号进行统一管理 version = [ compileSdkVersion: 30, minSdkVersion : 19, targetSdkVersion : 30, buildToolVersion : '28.0.3', versionCode : 100, versionName : "1.0.0", ] //依赖库版本号管理 libsVersion = [ gson = "2.8.5", glide = "4.6.1", rxlifecycle = "2.2.2", ] //依赖库管理 libsDependency = [ gson : "com.google.code.gson:gson:$rootProject.gson", glide : "com.github.bumptech.glide:glide:$rootProject.glide", rxlifecycle : "com.trello.rxlifecycle2:rxlifecycle:$rootProject.rxlifecycle", ] }
-
在app工程和组件的build.gradle文件中使用,拿
组件
目录下的build.gradle
文件中代码举例:apply plugin: 'com.android.library' //配置版本号 android { compileSdkVersion rootProject.ext.version.compileSdkVersion defaultConfig { minSdkVersion rootProject.ext.version.minSdkVersion targetSdkVersion rootProject.ext.version.targetSdkVersion buildToolsVersion rootProject.ext.version.buildToolVersion versionCode rootProject.ext.version.versionCode versionName rootProject.ext.version.versionName } } //依赖的包 dependencies { implementation(rootProject.ext.libsDependency.gson) implementation(rootProject.ext.libsDependency.glide) implementation(rootProject.ext.libsDependency.rxlifecycle) }
3.组件化中子模块交互方式
3.1 如果不使用ARouter,可以通过哪些方案实现组件化中各个子模块之间的通信?
- EventBus
- 缺点:EventBean对象维护成本太高,不好去管理
- 广播(Broadcast)
- 缺点:不好管理,都统一发送出去了
- 隐式意图
- 缺点:在AndroidManifest.xml里面配置,每个需要跨模块跳转的页面都需要配置intent-filter的action和category属性.
- 使用方式代码如下:
<!--清单文件中需要添加intent-filter过滤器,然后设置action和category--> <activity android:name="com.tangkun.action.ActionImplActivity"> <intent-filter> <action android:name="com.tangkun.myaction1" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> //在Activity中通过Intent传入Action名称来实现页面跳转 Intent intent = new Intent(); //设置的action名称需要和清单文件中通过action配置名称相同;还需要指定category,否则会报错 intent.setAction("com.tangkun.myaction1"); startActivity(intent);
- 类加载方式
- 缺点:容易写错包名或类名.
可以尝试通过这种方式
- 使用方式代码如下:
Class classLoad = Class.forName("com.tangkun.action.ActionImplActivity"); Intent intent = new Intent(this, classLoad); startActivity(intent);
- 缺点:容易写错包名或类名.
- 使用全局Map的方式
- 缺点:要注册很多的对象.
可以尝试通过这种方式
- 操作步骤:
- 在App工程中自定义Application中将所有的Activity都注册到base库中的一个集合中去,通过名字(如:MyInfoActivity)和Activity.class(比如:MyInfoActivity.class)
- 在组件中通过依赖的base库里的集合对象中,通过名称取出他的Class对象,然后通过Intent启动这个页面即可.
问题: 如果要移除某个组件,岂不是要将这个组件下所有添加到集合Activity从自定义Application给删除掉.
- 缺点:要注册很多的对象.
3.2 组件化、插件化和模块化的区别?
组件化:
组件之间可以自由组装与拆卸,以及重用,因为组件与组件之间没有直接关系插件化:
当前app通过加载从服务器下载的apk文件,然后解析出来在当前app中使用.- 比如:支付宝app中包含了很多插件(饿了么、菜鸟、哈啰这些都属于下载下来的插件apk)
- 存在问题:版本兼容,需要去适配目前各个系统版本存在的兼容性问题,还要担心Android系统升级带来的兼容性问题.
因为需要启动未注册的Activity,所以会用到Hook技术,在AMS检测启动页面时,将启动页面替换成一个已经注册的代理ProxyActivity,AMS检测后在执行生命周期之前替换成未注册的启动页面即可.
为了规避插件化带来的兼容性问题,这些app都替换成加载网页的形式,来实现展示别的app功能.
模块化:
组件化是为了重用而拆分模块,而模块化是为了业务的分离而拆分模块.
4.APT技术
注解处理器(Annotation Processing Tool
)
概念:
是一种处理注释的工具,他对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要注解处理器能够运行,必须要通过APT工具(继承他AbstractProcessor
重写process()
方法,然后在这个方法中进行处理)来进行处理.
也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解处理器才能执行.
通俗来讲:根据规则,帮我们生成代码、生成类文件.
运行期和编译其采用过APT技术的一些框架举例和比较:
-
运行期采用了APT技术的框架举例:
XUtils
特点: 需要在用户使用的时候才会去做解析工作,给用户带来了性能负担.因此,惨遭淘汰. -
编译期采用类APT技术的框架举例:
Butterknife、Dagger2、Room、DataBinding、ARouter
特点: 开发者通过开发工具在编译代码的时候,就会生成相应的代码.因此,不会影响到用户的体验.
5.高级用法JavaPoet
Poet /ˈpoʊət/ 诗人
- EventBus采用传统的方式一行一行生成代码.
我的理解:读取.java/.kt文件中的每一行代码进行解析,看是否使用到了EventBus的注解相关代码,然后再去解析注解内容,进行处理;应该就是观察者设计模式,被观察者发送消息然后观察者去处理消息
- ButterKnife和ARouter则采用JavePoet生成代码.
JavaPoet是什么?
JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件,这个框架的功能非常实用,也是我们习惯的Java面向对象OOP语法,可以很方便的使用他根据注解生成相应代码.
(EventBus实用)传统方式与JavaPoet区别?
传统方式:先导包,然后再写类,最后写方法,总之就是把我们的java文件中的代码一行一行的通过BufferWriter写进去
,比较符合正常使用逻辑;可读性强
JavaPoet先写方法,然后再写类,最后导包,是OOP面向对象思想,在复杂的场景下性能不及传统的方式.看起来费劲
JavaPoet使用方式:
- 通过自定义注解处理器继承自AbstractProcessor,然后重写process()方法,当我们在编译的时候,会调用这个方法,该方法中会对使用过带有
注解
(比如:过滤Arouter.class
)的类进行解析; - 在解析过程中,JavaPoet会根据方法、类、包、文件的顺序生成对应的
.class文件
,生成文件的目录是:
build/generated/sources/...
6.组件化项目部署,ARouter原理
从层次结构上分类:
- app壳工程
- 业务组件
- 在业务组件中的类中添加注解,比如:Activity添加ARouter注解
- 当编译的时候,就会将这个类的信息注册到ARouter路由表中
- 从而可以让一个和当前组件没有直接关系的组件,可以通过注册到路由表中的class信息,实现跨组件页面跳转.
- 公共基础库
- ARouter
- ARouter-annotatation 注解类所在lib
- ARouter-complier 用到了auto-service,然后继承自AbstractProcessor重写process()方法.当我们在编译的时候,就会根据注解生成相应的.class文件
7.Path和Group的映射关系
采用分组Group
的形式,来对这个组下面的Path
进行管理.
比如:我们的业务组件,在使用ARouter的时候,在对Activity/Fragment进行注解时,需要采用模块名+类名的形式.如:/user/PersonDetailActivity
当要通过ARouter路径跳转某个页面时,会根据路径解析出组名,对该组的页面路径进行遍历,找出跳转的页面.
分组的好处:不需要遍历所有的组下的路径对应的class,只需要遍历当前组下的路径列表,提高查询效率.
8.Path生成管理和Group生成管理
Path和Group生成步骤:
- 定义Path仓库和Group仓库,为后续存储做准备
- 准备工作:路由对象封装,校验作用域为Activity等
- 校验路由对象
- 第一大步:Path
- 第二大步:Group
总结:
在自定义AbstractProcessor
类重写的process
方法中,通过过滤出使用过@ARouter
注解的Activity
或Fragment
,然后通过JavaPoet
给他们生成.java
文件,
(JavaPoet采用OOP思想生成Java代码并存放在文件里,生成规则是按这个顺序:方法、类、包、文件
)
当我们在编译的时候,就会生成.class
文件,存放在如下目录:
build/generated/sources/...
由于ARouter
框架是由公共基础库
所依赖,所以我们依赖了基础组件库的组件就可以通过组名(Group
)和路径名(Path
)找到启动页面的Class
类,然后我们可以通过Intent
启动该Class
类实现跨组件页面跳转.