组件化中路由框架学习笔记
在组件化之前的一种业务业务划分架构是一种单一分层的结构,整个APP是一个Module,不同的业务拆分在不同的包下:
- 不管分包做的多好,随着项目的增大,项目会失去层次感,导致接受项目时会比较吃力。(臃肿)
- 包名约束是一种弱约束,一不小心就会导致不同的包名之间相互引用,导致包名之间耦合度高。(耦合度高)
- 多人联合开发中,在版本管理中很容易出现代码冲突和代码覆盖的问题。(版本管理困难)
组件化是一个化繁为简的过程,将多个功能模块进行拆分、重组的过程。即将APP按照业务划分为不同的模块,最后在打包为完整的APP时再整合为一起。
image.png
如上图所示:可分为APP壳工程、业务组件层、功能组件层和基础库层。
APP壳工程负责管理各个业务组件和打包APK,没有具体的业务功能。
业务组件层是根据不同的业务拆分的不同的业务组件。
功能组件层是为上层提供基础的功能服务。
基础库中包含了各种开源库以及和业务无关的各种自研工具库。
比如可以新建项目如下:
image.png切换到 project视图,与app同目录,建立business文件夹,然后在其中建立两个Android Library,分比为food和waimai。
新建config.gradle文件,在其中定义变量:
ext {
//标识是以组件化模式运行还是集成化模式运行,
// 如果是true,以集成化方式运行,如果是false,以组件化方式运行
isModule = false
}
isModule用来标识food是以Android 项目运行还是作为一个普通的Android Module。
在project 的build.gradle文件中引入该文件:
apply from : "config.gradle"
分别修改app、food和waimai的build.gradle文件:
app的build.gradle文件:
///根据变量的取值来决定运行方式
if(rootProject.ext.isModule){
apply plugin: 'com.android.library'
}else{
apply plugin: 'com.android.application'
}
....
defaultConfig {
if(!rootProject.ext.isModule){
applicationId "com.example.zujianhuapro"
}
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
....
food的build.gradle文件:
if (rootProject.ext.isModule) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
...
defaultConfig {
if (rootProject.ext.isModule) {
applicationId "com.example.food"
}
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
...
waimai的同上。
可自行改变isModule 的值查看效果。
为什么要使用组件化
- 各个组件专注自身功能的实现,模块中代码高度内聚,只负责一项任务,也就是常说的单一职责原则。
- 各业务研发可以互不干扰,提升协作效率。
- 业务组件可以进行插拔,灵活多变。
- 业务组件之间将不再直接应用和依赖,各个业务模块组件更加独立,降低耦合。
- 加快编译速度,提高开发效率。
最简化最核心的就是动态的切换library和application。
组件化最需要解决的问题--页面跳转
路由跳转一般可采用隐式跳转和显示跳转两种方式,但是在组件化结构中,因为不同组件不会相互依赖,所以无法采用显示跳转的方式,只可以采用隐式跳转,但是隐式跳转也存在问题,使用隐式跳转时,必须先在生命文件中用intent-filter来限定隐式Action的启动,其他的Module才可以使用隐式的Action跳转到响应的Activity。但是在组件化中使用这种方式并不友好,不仅多人开发困难,还存在安全隐患。
而路由组件正是为此而存在的。
image.png首先需要得到目标的页面地址,然后在路由表中寻址,找到目标页后,得到Activity的Class对象,然后启动目标页。
可以看出,路由组件的关键在于路由表,而路由表就是一系列特定的URL和特定的Activity之间的映射集合,是一个Map结构,key是一个字符串即URL,value是Activity的Class对象。
路由表需要保证只有一份,所以需要使用单例模式,而路由框架需要被所有的组件依赖到。
在项目中新建路由Module,如下所示
image.png
在app、food和waimai中引入改Module,
implementation project(':router-api')
在路由Module中新建如下类
/**
* 项目名称 zujianhuaPro
* 创建人 xiaojinli
* 创建时间 2020/8/29 9:31 AM
* 路由表,需要被所有的组件依赖,所以需要使用单例模式
**/
public class Router {
private static Router mRouter;
private static Context mContext;
//路由表
private static Map<String,Class<? extends Activity>> routers = new HashMap<>();
public void init(Application application){
mContext = application;
}
public static Router getInstance(){
if(mRouter == null){
synchronized (Router.class){
if(mRouter == null){
mRouter = new Router();
}
}
}
return mRouter;
}
//向路由表中注册一个Activity
public void register(String path,Class<? extends Activity> cls){
routers.put(path,cls);
}
//启动路由表中的一个Activity
public void startActivity(String path){
Class<? extends Activity> cls = routers.get(path);
if(cls == null){
return;
}
Intent intent = new Intent(mContext,cls);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
}
关键方法有三个,一个是实现单例模式方法,因为需要被所有的业务Module引用,所以需要保持唯一性。二是向路由表中注册一个页面的register方法,三是启动一个路由表中页面的方法。
使用时如下所示:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Router.getInstance().init(getApplication());
Router.getInstance().register("/food/FoodActivity", FoodActivity.class);
Router.getInstance().register("/waimai/WaiMaiActivity", WaiMaiActivity.class);
}
public void jumpToFood(View view) {
Router.getInstance().startActivity("/food/FoodActivity");
}
}
在壳Activity中初始化路由组件以及注册页面信息,就可实现跳转。
在food组件中跳转到waimai组件可使用如下方式
Router.getInstance().startActivity("/waimai/WaiMaiActivity");
在waimai组件中跳转到food组件可使用如下方式
Router.getInstance().startActivity("/food/FoodActivity");
但是这种方式存在一个明显的缺点,即我们需要自己在路由表中注册所有的页面,不易维护。
下面我们会通过APT来进行优化。
什么是APT
APT是注解处理器,是javac处理注解的一种工具,他用来在编译时扫描和处理注解,简单来说就是在编译期间,通过注解采集信息,生成.java文件,减少重复代码的编写。很多框架都是用了APT,包括Butterknife和Glide等。
通过上面的路由表可以看出,我们需要的关键信息是Activity的一个字符串参数以及Activity的Class对象,我们都可以通过注解来采集到。
创建JavaLib,依赖下面的依赖监控到编译期
//构建 -----》》》》----【编译时期】------》》》打包-------》》》安装
// As-3.4.1 + gradle-5.1.1-all + auto-service:1.0-rc4
//在编译器可以通过以下依赖让我们自定义的AbstractProcessor工作,即可以通过这两个服务让我们在编译器做一些事情
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
首先我们需要创建一个JavaLib,来创建一个注解
@Target(ElementType.TYPE) //作用在类上
@Retention(RetentionPolicy.CLASS) //保留到CLASS,因为我们在编译器需要,所以需要保留到Class
public @interface Route {
String value(); //详细路径名,比如/main/MainActivity,接收一个字符串参数
String group() default "";//路由组名,比如main
}
其次我们需要再定义一个JavaLib,来创建一个处理注解的类,需要继承AbstractProcessor,默认实现process方法。需要注意添加如下注解:
//自动注册,以便该类可以在编译器干活
@AutoService(Processor.class)
//允许支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE)
//指定JDK编译版本,必须写
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//注解处理器接收的参数
@SupportedOptions({ProcessorConfig.OPTIONS,ProcessorConfig.APT_PACKAGE})
此外我们需要在其他的Module中引入该处理器Module,注意需要使用annotationProcessor关键字
annotationProcessor project(path: ':route_compile')
待一切都准备完成之后,点击锤子符号即Make Project按钮,如果一切都没有问题,处理器文件夹下会出现build文件夹,在其中可以找到如下类,意味着注解处理器配置成功。
image.png此时当我们项目进入编译器时,会在APP工程中扫描引入该注解处理器module的组件中的类,查看是否用使用了Route注解。下面就是在我们自定义的注解处理器内添加相应 的逻辑生成需要的java文件。
完整的项目地址: