android开发package组件,Android组件化开发

组件化设置

config.gradle(组件化统一配置文件)

7107d5d167da

image-config.png

不熟悉groovy语法的可以简单了解下

7107d5d167da

image-module.png

这里记得在项目根目录的build.gradle中添加配置文件config.gradle否则在项目中无法引用config.gradle中定义的属性

module中build.gradle配置

/**

*isRelease是config.gradle定义的组件化开关

*这里非常重要组件化的关键就是各个module是否可以单独运行

**/

if (isRelease) {

//当需要集成化编译运行是isRelease为true各module变为android library依赖,否则为application

apply plugin: 'com.android.library'

} else {

apply plugin: 'com.android.application'

}

//config.gradle定义的各个属性

def androidID = rootProject.ext.androidID//android配置

def applicationID = rootProject.ext.applicationID//应用唯一标识

def dependenciesSupport = rootProject.ext.dependencies//依赖

android {

compileSdkVersion androidID.compileSdkVersion

buildToolsVersion androidID.buildToolsVersion

defaultConfig {

if (!isRelease) {

applicationId applicationID.personal

}

minSdkVersion androidID.minSdkVersion

targetSdkVersion androidID.targetSdkVersion

versionCode androidID.versionCode

versionName androidID.versionName

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

// 这个方法接收三个非空的参数,第一个:确定值的类型,第二个:指定key的名字,第三个:传值(必须是String)

// 为什么需要定义这个?因为src代码中有可能需要用到跨模块交互,如果是组件化模块显然不行

// 切记:不能在android根节点,只能在defaultConfig或buildTypes节点下

buildConfigField("boolean", "isRelease", String.valueOf(isRelease))

// 在gradle文件中配置选项参数值(用于APT传参接收)

// 切记:必须写在defaultConfig节点下

javaCompileOptions {

annotationProcessorOptions {

arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT]

}

}

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

}

}

compileOptions {

sourceCompatibility javaVersion

targetCompatibility javaVersion

}

}

dependencies {

//循环依赖第三方库

//这里用了groovy中的foreach一句话依赖config.gradle定义的依赖集合

dependenciesSupport.each { k, v -> implementation v }

testImplementation testJunit

androidTestImplementation androidTestJunit

androidTestImplementation androidTestEspresso

implementation project(':common')

implementation project(':arouter_annotation')

annotationProcessor project(':arouter_compiler')

}

组件化路由(跨模块交互)

自定义路由注解@ARouter

阅读阿里ARouter框架源码后发现太过于繁琐并且性能低下(使用了线程池遍历所有dex分包下的apt生成文件很low)而且很久也没有更新维护了,自己写了一个路由框架

package com.test.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.TYPE)//注解作用域类、接口上

@Retention(RetentionPolicy.CLASS)//运行时为预编译

public @interface ARouter {

String path();//详细路径

String group() default "";//组名,可不填从详细路径截取要求开发者按照规范定义详细路径

}

使用@ARouter注解实例:

//必须按照统一格式来定义path=/module名/类名,group=module名(可省略)

@ARouter(path ="/app/MainActivity",group = "app")

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (BuildConfig.isRelease) {

Log.e(Cons.TAG, "当前为:集成化模式,除app可运行,其他子模块都是Android Library");

} else {

Log.e(Cons.TAG, "当前为:组件化模式,app/order/personal子模块都可独立运行");

}

}

}

自定义路由注解处理器ARouterProcessor

package com.test.compiler;

import com.google.auto.service.AutoService;

import com.squareup.javapoet.ClassName;

import com.squareup.javapoet.JavaFile;

import com.squareup.javapoet.MethodSpec;

import com.squareup.javapoet.ParameterizedTypeName;

import com.squareup.javapoet.TypeName;

import com.squareup.javapoet.TypeSpec;

import com.squareup.javapoet.WildcardTypeName;

import com.test.annotation.ARouter;

import com.test.annotation.model.RouterBean;

import com.test.compiler.utils.Constants;

import com.test.compiler.utils.EmptyUtils;

import java.io.IOException;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;

import javax.annotation.processing.Filer;

import javax.annotation.processing.Messager;

import javax.annotation.processing.ProcessingEnvironment;

import javax.annotation.processing.Processor;

import javax.annotation.processing.RoundEnvironment;

import javax.annotation.processing.SupportedAnnotationTypes;

import javax.annotation.processing.SupportedOptions;

import javax.annotation.processing.SupportedSourceVersion;

import javax.lang.model.SourceVersion;

import javax.lang.model.element.Element;

import javax.lang.model.element.Modifier;

import javax.lang.model.element.TypeElement;

import javax.lang.model.type.TypeMirror;

import javax.lang.model.util.Elements;

import javax.lang.model.util.Types;

import javax.tools.Diagnostic;

@AutoService(Processor.class)

@SupportedAnnotationTypes({Constants.AROUTER_ANNOTATION_TYPES})

@SupportedSourceVersion(SourceVersion.RELEASE_8)

@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})

public class ARouterProcessor extends AbstractProcessor {

Elements elementUtils;

Types typeUtils;

Filer filer;

Messager messager;

String moduleName;

String packageNameForAPT;

// 临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历

// key:组名"app", value:"app"组的路由路径"ARouter$$Path$$app.class"

private Map> tempPathMap = new HashMap<>();

// 临时map存储,用来存放路由Group信息,生成路由组类文件时遍历

// key:组名"app", value:类名"ARouter$$Path$$app.class"

private Map tempGroupMap = new HashMap<>();

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

elementUtils = processingEnvironment.getElementUtils();

typeUtils = processingEnvironment.getTypeUtils();

filer = processingEnvironment.getFiler();

messager = processingEnvironment.getMessager();

// 通过ProcessingEnvironment去获取对应的参数

Map options = processingEnvironment.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, "packageName >>> " + packageNameForAPT);

}

// 必传参数判空(乱码问题:添加java控制台输出中文乱码)

if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {

throw new RuntimeException("注解处理器需要的参数moduleName或者packageName为空,请在对应build.gradle配置参数");

}

}

@Override

public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {

if (!EmptyUtils.isEmpty(set)) {

Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);

if (!EmptyUtils.isEmpty(elements)) {

// 解析元素

try {

parseElements(elements);

return true;

} catch (IOException e) {

e.printStackTrace();

}

}

// 坑:必须写返回值,表示处理@ARouter注解完成

return true;

}

return false;

}

private void parseElements(Set extends Element> elements) 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();

// 遍历节点

for (Element element : elements) {

// 获取每个元素类信息,用于比较

TypeMirror elementMirror = element.asType();

messager.printMessage(Diagnostic.Kind.NOTE, "遍历元素信息:" + elementMirror.toString());

// 获取每个类上的@ARouter注解中的注解值

ARouter aRouter = element.getAnnotation(ARouter.class);

// 路由详细信息,最终实体封装类

RouterBean bean = new RouterBean.Builder()

.setGroup(aRouter.group())

.setPath(aRouter.path())

.setElement(element)

.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.AROUTER_GROUP); // 组接口

TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTER_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;

TypeName methodReturns = ParameterizedTypeName.get(

ClassName.get(Map.class), // Map

ClassName.get(String.class), // Map

ClassName.get(RouterBean.class) // Map

);

// 遍历分组,每一个分组创建一个路径类文件,如:ARouter$$Path$$app

for (Map.Entry> entry : tempPathMap.entrySet()) {

// 方法配置:public Map loadPath() {

MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME) // 方法名

.addAnnotation(Override.class) // 重写注解

.addModifiers(Modifier.PUBLIC) // public修饰符

.returns(methodReturns); // 方法返回值

// 遍历之前:Map pathMap = new HashMap<>();

methodBuidler.addStatement("$T $N = new $T<>()",

ClassName.get(Map.class),

ClassName.get(String.class),

ClassName.get(RouterBean.class),

Constants.PATH_PARAMETER_NAME,

HashMap.class);

// 一个分组,如:ARouter$$Path$$app。有很多详细路径信息,如:/app/MainActivity、/app/OtherActivity

List pathList = entry.getValue();

// 方法内容配置(遍历每个分组中每个路由详细路径)

for (RouterBean bean : pathList) {

// 类似String.format("hello %s qpl123 %d", "qpl", 123)通配符

// pathMap.put("/app/MainActivity", RouterBean.create(

// RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));

methodBuidler.addStatement(

"$N.put($S, $T.create($T.$L, $T.class, $S, $S))",

Constants.PATH_PARAMETER_NAME, // pathMap.put

bean.getPath(), // "/app/MainActivity"

ClassName.get(RouterBean.class), // RouterBean

ClassName.get(RouterBean.Type.class), // RouterBean.Type

bean.getType(), // 枚举类型:ACTIVITY

ClassName.get((TypeElement) bean.getElement()), // MainActivity.class

bean.getPath(), // 路径名

bean.getGroup() // 组名

);

}

// 遍历之后:return pathMap;

methodBuidler.addStatement("return $N", Constants.PATH_PARAMETER_NAME);

// 最终生成的类文件名

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)) // 实现ARouterLoadPath接口

.addModifiers(Modifier.PUBLIC) // public修饰符

.addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)

.build()) // 类构建完成

.build() // JavaFile构建完成

.writeTo(filer); // 文件生成器开始生成类文件

// 非常重要一步!!!!!路径文件生成出来了,才能赋值路由组tempGroupMap

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;

TypeName methodReturns = ParameterizedTypeName.get(

ClassName.get(Map.class), // Map

ClassName.get(String.class), // Map

// 第二个参数:Class extends ARouterLoadPath>

// 某某Class是否属于ARouterLoadPath接口的实现类

ParameterizedTypeName.get(ClassName.get(Class.class),

WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))

);

// 方法配置:public Map> loadGroup() {

MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名

.addAnnotation(Override.class) // 重写注解

.addModifiers(Modifier.PUBLIC) // public修饰符

.returns(methodReturns); // 方法返回值

// 遍历之前:Map> groupMap = new HashMap<>();

methodBuidler.addStatement("$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,

HashMap.class);

// 方法内容配置

for (Map.Entry entry : tempGroupMap.entrySet()) {

// 类似String.format("hello %s qpl123 %d", "qpl", 123)通配符

// groupMap.put("main", ARouter$$Path$$app.class);

methodBuidler.addStatement("$N.put($S, $T.class)",

Constants.GROUP_PARAMETER_NAME, // groupMap.put

entry.getKey(),

// 类文件在指定包名下

ClassName.get(packageNameForAPT, entry.getValue()));

}

// 遍历之后:return groupMap;

methodBuidler.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);

// 最终生成的类文件名

String finalClassName = Constants.GROUP_FILE_NAME + moduleName;

messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +

packageNameForAPT + "." + finalClassName);

// 生成类文件:ARouter$$Group$$app

JavaFile.builder(packageNameForAPT, // 包名

TypeSpec.classBuilder(finalClassName) // 类名

.addSuperinterface(ClassName.get(groupLoadType)) // 实现ARouterLoadGroup接口

.addModifiers(Modifier.PUBLIC) // public修饰符

.addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)

.build()) // 类构建完成

.build() // JavaFile构建完成

.writeTo(filer); // 文件生成器开始生成类文件

}

/**

* 赋值临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历

*

* @param bean 路由详细信息,最终实体封装类

*/

private void valueOfPathMap(RouterBean bean) {

if (checkRouterPath(bean)) {

messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());

// 开始赋值Map

List routerBeans = tempPathMap.get(bean.getGroup());

// 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map

if (EmptyUtils.isEmpty(routerBeans)) {

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) {

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;

}

}

这里使用了apt+javapoet(超级利刃)在预编译阶段生成代码,不了解的同学可以自行百度了解下,有时间的话会把完整的ARouter上传github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值