为什么要组件化开发
如何组件化
通过一个config.gradle文件,配置是否开启组件化, 并且统一控制版本。
ext {
// 定义一个项目全局变量isRelease,用于动态切换:组件化模式 / 集成化模式
// false: 组件化模式(子模块可以独立运行),true :集成化模式(打包整个项目apk,子模块不可独立运行)
isRelease = true
// 包名,用于存放APT生成的类文件
packageNameForAPT = "com.sanguine.gradle.demo.one"
androidId = [
compileSdkVersion: 29,
buildToolsVersion: "29.0.1",
minSdkVersion : 21,
targetSdkVersion : 29,
versionCode : 1,
versionName : "1.0.0.1"
]
appcompatVersion = "1.1.0"
ids = [
"app" : "com.sanguine.gradle.demo.one",
"personal": "com.sanguine.gradle.demo.modular.personal",
"order" : "com.sanguine.gradle.demo.modular.order"
]
dependencies = [
"appcompat" : "androidx.appcompat:appcompat:${appcompatVersion}",
"recyclerview": "com.android.support:recyclerview-v7:${appcompatVersion}",
"constraintlayout": "androidx.constraintlayout:constraintlayout:1.1.3",
"okhttp3" : "com.squareup.okhttp3:okhttp:3.10.0",
"retrofit" : "com.squareup.retrofit2:retrofit:2.5.0",
"fastjson" : "com.alibaba:fastjson:1.2.58",
]
}
其它module中,使用统一版本的即可,例如order的module中,build.gradle文件可以这么写
if (isRelease) { // 如果是发布版本时,各个模块都不能独立运行
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
def rootApplicationId = rootProject.ext.androidId
def supportDependencies = rootProject.ext.dependencies
def appId = rootProject.ext.ids
android {
compileSdkVersion rootApplicationId.compileSdkVersion
buildToolsVersion rootApplicationId.buildToolsVersion
defaultConfig {
if (!isRelease){
applicationId appId.order
}
minSdkVersion rootApplicationId.minSdkVersion
targetSdkVersion rootApplicationId.targetSdkVersion
versionCode rootApplicationId.versionCode
versionName rootApplicationId.versionName
// 这个方法接收三个非空的参数,第一个:确定值的类型,第二个:指定key的名字,第三个:传值(必须是String)
// 为什么需要定义这个?因为src代码中有可能需要用到跨模块交互,如果是组件化模块显然不行
// 切记:不能在android根节点,只能在defaultConfig或buildTypes节点下
buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 遍历添加同一版本的依赖
supportDependencies.each{ K,V -> implementation V}
implementation project(':lib') // 公共基础库
}
这样,每个子module都这么定义的话, 如果开启了模块化,则在build.gradle文件,开头使用apply plugin: ‘com.android.application’,且增加appid属性,否则开头使用apply plugin: ‘com.android.library’,且没有appid属性。需要注意的是app这个module,因为是个壳工程,所以其build.gradle比较特殊一点,如下所示
apply plugin: 'com.android.application'
def rootApplicationId = rootProject.ext.androidId
def supportDependencies = rootProject.ext.dependencies
def appId = rootProject.ext.ids
android {
compileSdkVersion rootApplicationId.compileSdkVersion
buildToolsVersion rootApplicationId.buildToolsVersion
defaultConfig {
// app子模块在组件化开发中,默认为application
applicationId appId.app
minSdkVersion rootApplicationId.minSdkVersion
targetSdkVersion rootApplicationId.targetSdkVersion
versionCode rootApplicationId.versionCode
versionName rootApplicationId.versionName
// 这个方法接收三个非空的参数,第一个:确定值的类型,第二个:指定key的名字,第三个:传值(必须是String)
// 为什么需要定义这个?因为src代码中有可能需要用到跨模块交互,如果是组件化模块显然不行
// 切记:不能在android根节点,只能在defaultConfig或buildTypes节点下
buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 循环引入第三方库
supportDependencies.each { K, V -> implementation V }
implementation project(':lib') // 公共基础库
// 如果是集成化模式,做发布版本时。各个模块都不能独立运行了
if (isRelease) {
implementation project(':order')
implementation project(':personal')
}
}
组件化交互
因为各个子模块不应该相互依赖增加耦合度,所以子模块交互是组件化开发最重要的点。常见的子模块之间的交互方式有
- 全局Map,将各个子模块的Activity都以key-value的形式存在全局的Map中
需要开发者手动将Activity注册到全局的Map中,工作量打,且不易维护。 - 反射,通过反射技术找到Activity的Class,从而交互。
容易出现拼写错误,如果Activity的路径修改了,需要修改代码适用新的路径。
apt+javapoet技术生成辅助类
本文中使用apt+javapoet技术,动态生成辅助类,从辅助类中取的Activity的Class,从而实现交互。最终实现的类如下所示:
public class ARouter$$Group$$order implements ARouterLoadGroup {
@Override
public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
groupMap.put("order", ARouter$$Path$$order.class);
return groupMap;
}
}
ARouter$$Group$$order
这个类用来以模块名为key,查找该模块中使用了注解的所有类。
public class ARouter$$Path$$order implements ARouterLoadPath {
@Override
public Map<String, RouterBean> loadPath() {
Map<String, RouterBean> pathMap = new HashMap<>();
pathMap.put("/order/getUserInfo", RouterBean.create(RouterBean.Type.CALL, OrderUserImpl.class, "/order/getUserInfo", "order"));
pathMap.put("/order/Order_Main2Activity", RouterBean.create(RouterBean.Type.ACTIVITY, Order_Main2Activity.class, "/order/Order_Main2Activity", "order"));
pathMap.put("/order/Order_MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, Order_MainActivity.class, "/order/Order_MainActivity", "order"));
return pathMap;
}
}
ARouter$$Path$$order
这个类用以path名为key,查找该path注册的Activity的信息,包括type,class,path路径,group名。以此可以通过path即可跳转到相应的Activity了。
注解类
新增java lib的module,编写注解类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {
/**
* 路由的路径
*
* @return
*/
String path();
/**
* 路由的组名
*
* @return
*/
String group() default "";
}
group可以不传值, 可以通过配置信息读取到group的值。
使用的时候,直接在想注册的Activity的上面增加这个注解。
@ARouter(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
例如这样, 那么这个MainActivity 的注册path就为“/app/MainActivity”。
注解处理工具
使用注解处理工具类,处理ARouter 这个注解。新建java lib的module,配置javapoet依赖,并且解决控制台乱码问题。
这个module的gradle文件如下所示:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
// 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.9.0"
// 引入annotation,处理@ARouter注解
implementation project(':annotation')
}
// java控制台输出中文乱码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "7"
targetCompatibility = "7"
然后新建一个注解处理工具类。如下所示。
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.AROUTER_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数,从每个module中的build.gradle文件中赋值
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {
// 操作Element工具类 (类、函数、属性都是Element)
private Elements elementUtils;
// type(类信息)工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
private Filer filer;
// 子模块名,如:app/order/personal。需要拼接类名时用到(必传)ARouter$$Group$$order
private String moduleName;
// 包名,用于存放APT生成的类文件
private String packageNameForAPT;
// 临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
// key:组名"app", value:"app"组的路由路径"ARouter$$Path$$app.class"
private Map<String, List<RouterBean>> tempPathMap = new HashMap<>();
// 临时map存储,用来存放路由Group信息,生成路由组类文件时遍历
// key:组名"app", value:类名"ARouter$$Path$$app.class"
private Map<String, String> tempGroupMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
typeUtils = processingEnv.getTypeUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
Map<String, String> options = processingEnv.getOptions();
if (!EmptyUtils.isEmpty(options)) {
moduleName = options.get(Constants.MODULE_NAME);
packageNameForAPT = options.get(Constants.APT_PACKAGE);
// 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e
messager.printMessage(Diagnostic.Kind.NOTE, "moduleName >>> " + moduleName);
messager.printMessage(Diagnostic.Kind.NOTE, "packageNameForAPT >>> " + packageNameForAPT);
}
// 必传参数判空(乱码问题:添加java控制台输出中文乱码)
if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
throw new RuntimeException("注解处理器需要的参数moduleName或者packageName为空,请在对应build.gradle配置参数");
}
}
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param annotations 使用了支持处理注解的节点集合
* @param roundEnv 当前或是之前的运行环境,可以通过该对象查找的注解。
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (EmptyUtils.isEmpty(annotations) || roundEnv == null) {
return false;
}
// 所有被@ARouter注解的元素集合
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ARouter.class);
// 解析元素
try {
parseElements(set);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
/**
* 解析所有被 @ARouter 注解的 类元素集合
*
* @param annotations
*/
private void parseElements(Set<? extends Element> annotations) throws IOException {
// 通过Element工具类,获取Activity、Callback类型
TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
TypeElement callType = elementUtils.getTypeElement(Constants.CALL);
// 显示类信息(获取被注解节点,类节点)这里也叫自描述 Mirror
TypeMirror activityMirror = activityType.asType();
TypeMirror callMirror = callType.asType();
// 遍历所有@ARouter注解的元素
for (Element element : annotations) {
if (element != null) {
// 获取每个元素类信息,用于比较,比较是否是Activity
TypeMirror elementMirror = element.asType();
messager.printMessage(Diagnostic.Kind.NOTE, "遍历元素信息:" + elementMirror.toString());
// 获取每个类上的@ARouter注解中的注解值
ARouter aRouter = element.getAnnotation(ARouter.class);
// 路由详细信息,最终实体封装类
RouterBean bean = new RouterBean.Builder()
.setElement(element)
.setGroup(aRouter.group())
.setPath(aRouter.path())
.build();
// 高级判断:ARouter注解仅能用在类之上,并且是规定的Activity
// 类型工具类方法isSubtype,相当于instance一样
if (typeUtils.isSubtype(elementMirror, activityMirror)) {
bean.setType(RouterBean.Type.ACTIVITY);
}else if(typeUtils.isSubtype(elementMirror,callMirror)){
bean.setType(RouterBean.Type.CALL);
} else {
// 不匹配抛出异常,这里谨慎使用!考虑维护问题
throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
}
// 赋值临时map存储,用来存放路由组Group对应的详细Path类对象
valueOfPathMap(bean);
}
}
// routerMap遍历后,用来生成类文件
// 获取ARouterLoadGroup、ARouterLoadPath类型(生成类文件需要实现的接口)
TypeElement groupLoadType = elementUtils.getTypeElement(Constants.AROUTE_GROUP); // 组接口
TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTE_PATH); // 路径接口
// 第一步:生成路由组Group对应详细Path类文件,如:ARouter$$Path$$app
createPathFile(pathLoadType);
// 第二步:生成路由组Group类文件(没有第一步,取不到类文件),如:ARouter$$Group$$app
createGroupFile(groupLoadType, pathLoadType);
}
/**
* 生成路由组Group对应详细Path,如:ARouter$$Path$$app
*
* @param pathLoadType ARouterLoadPath接口信息
*/
private void createPathFile(TypeElement pathLoadType) throws IOException {
// 判断是否有需要生成的类文件
if (EmptyUtils.isEmpty(tempPathMap)) return;
/**
* Map<String, RouterBean> loadPath();
*/
// 定义返回值类型
TypeName returnType = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouterBean.class)
);
// 遍历分组,每一个分组创建一个路径类文件,如:ARouter$$Path$$app
for (Map.Entry<String, List<RouterBean>> entry : tempPathMap.entrySet()) {
// 方法配置:public Map<String, RouterBean> loadPath() {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(returnType);
// 遍历之前:Map<String, RouterBean> pathMap = new HashMap<>();
methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouterBean.class),
Constants.PATH_PARAMETER_NAME,
ClassName.get(HashMap.class));
// 开始遍历
// 一个分组,如:ARouter$$Path$$app。有很多详细路径信息,如:/app/MainActivity、/app/OtherActivity
List<RouterBean> pathList = entry.getValue();
for (RouterBean bean : pathList) {
// 类似String.format("hello %s net163 %d", "net", 163)通配符
// pathMap.put("/app/MainActivity", RouterBean.create(
// RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
methodBuilder.addStatement("$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
Constants.PATH_PARAMETER_NAME,// N
bean.getPath(),// S
ClassName.get(RouterBean.class),// T
ClassName.get(RouterBean.Type.class),// T
bean.getType(),// L
ClassName.get((TypeElement) bean.getElement()), // T
bean.getPath(), // S
bean.getGroup()); // S
}
// 遍历结束
// return groupMap;
methodBuilder.addStatement("return $N", Constants.PATH_PARAMETER_NAME);
// 最终生成的类文件名
// entry.getKey() = bean.getGroup();,所以最后一个module一个文件
String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
packageNameForAPT + "." + finalClassName);
// 生成类文件:ARouter$$Path$$app
JavaFile.builder(packageNameForAPT,
TypeSpec.classBuilder(finalClassName)
.addSuperinterface(ClassName.get(pathLoadType))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build())
.build()
.writeTo(filer);
// 非常重要一步!!!!!路径文件生成出来了,才能赋值路由组tempGroupMap
// 用以生成组Group的类文件
tempGroupMap.put(entry.getKey(), finalClassName);
}
}
/**
* 生成路由组Group文件,如:ARouter$$Group$$app
*
* @param groupLoadType ARouterLoadGroup接口信息
* @param pathLoadType ARouterLoadPath接口信息
*/
private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
// 判断是否有需要生成的类文件
if (EmptyUtils.isEmpty(tempGroupMap) || EmptyUtils.isEmpty(tempPathMap)) return;
// Map<String, Class<? extends ARouterLoadPath>>
// 方法返回值
TypeName methodReturn = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
// 某某Class是否属于ARouterLoadPath接口的实现类
ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
);
// 方法声明
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(methodReturn);
// 方法体
// Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
Constants.GROUP_PARAMETER_NAME,
ClassName.get(HashMap.class));
// 方法体
// groupMap.put("order", ARouter$$Path$$order.class);
// 方法内容配置
for (Map.Entry<String, String> entry : tempGroupMap.entrySet()) {
// 类似String.format("hello %s net163 %d", "net", 163)通配符
// groupMap.put("main", ARouter$$Path$$app.class);
methodBuilder.addStatement("$N.put($S, $T.class)",
Constants.GROUP_PARAMETER_NAME, // groupMap.put
entry.getKey(),
// 类文件在指定包名下
ClassName.get(packageNameForAPT, entry.getValue()));
}
// 方法体
// return groupMap;
methodBuilder.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);
// 最终生成的类文件名
String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
packageNameForAPT + "." + finalClassName);
JavaFile.builder(packageNameForAPT,
TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ClassName.get(groupLoadType))
.addMethod(methodBuilder.build()).build())
.build().writeTo(filer);
}
/**
* 赋值临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
*
* @param bean 路由详细信息,最终实体封装类
*/
private void valueOfPathMap(RouterBean bean) {
// 校验注解入参的合法性, 要以"/"开头, group的名字要和module的名字一致
if (checkRouterPath(bean)) {
messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
// 从map中找key为bean.getGroup()的数据
List<RouterBean> routerBeans = tempPathMap.get(bean.getGroup());
if (EmptyUtils.isEmpty(routerBeans)) {
// 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
routerBeans = new ArrayList<>();
routerBeans.add(bean);
tempPathMap.put(bean.getGroup(), routerBeans);
} else {
// 找到了key,直接加入List集合
routerBeans.add(bean);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
}
}
/**
* 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
*
* @param bean 路由详细信息,最终实体封装类
*/
private boolean checkRouterPath(RouterBean bean) {
if (bean == null) {
return false;
}
String group = bean.getGroup();
String path = bean.getPath();
// @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
return false;
}
// 比如开发者代码为:path = "/MainActivity",最后一个 / 符号必然在字符串第1位
if (path.lastIndexOf("/") == 0) {
// 架构师定义规范,让开发者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
return false;
}
// 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app 作为group
String finalGroup = path.substring(1, path.indexOf("/", 1));
// @ARouter注解中的group有赋值情况
if (!EmptyUtils.isEmpty(group) && !group.equals(moduleName)) {
// 架构师定义规范,让开发者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
return false;
} else {
bean.setGroup(finalGroup);
}
return true;
}
}
最终rebuild项目之后 , 就可以生成辅助类了,使用辅助类即可实现各个子模块之间的交互。