android组件化管理单例,Android组件化实现方案(二)

APT(Annotation Processing Tool),根据注解自动给生成代码。

JavaPoet,代码生成框架。

要自动生成类文件,JavaPoet并不是必须的,比如JavaPoet的Example的一段代码,想要生成如下类文件:

package com.example.helloworld;

public final class HelloWorld {

public static void main(String[] args) {

System.out.println("Hello, JavaPoet!");

}

}

不使用JavaPoet需要这样写:

JavaFileObject sourceFile = filer.createSourceFile(someFile);

Writer writer = sourceFile.openWriter();

writer.write("package com.example.helloworld;\n");

writer.write("public final class HelloWorld {\n");

writer.write("public static void main(String[] args) {\n");

writer.write("System.out.println(\"Hello, JavaPoet!\");\n");

writer.write("}\n");

writer.write("} \n");

writer.close();

使用JavaPoet是这样写的:

MethodSpec main = MethodSpec.methodBuilder("main")

.addModifiers(Modifier.PUBLIC, Modifier.STATIC)

.returns(void.class)

.addParameter(String[].class, "args")

.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")

.build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")

.addModifiers(Modifier.PUBLIC, Modifier.FINAL)

.addMethod(main)

.build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)

.build();

javaFile.writeTo(someFile);

回到组件化工程,首选要明确希望自动生成的类长什么样子,这里需要的是一个Activity的对应关系Map,比如Order模块的路由这样的:

public class Router$$Path$$app implements RouterLoadPath {

@Override

public Map loadPath() {

Map pathMap = new HashMap<>();

pathMap.put("MainActivity", MainActivity.class);

//该模块有几个Activity需要被其他模块访问,都要加进去

//pathMap.put(...);

//pathMap.put(...);

return pathMap;

}

}

RouterLoadPath接口是为了不需要知道具体类名就可以调用loadPath(),下面会在提到。

这样就可以根据模块名和类名拿到对应的Activity了。

明确目标之后,就要开始APT和JavaPoet的工作了:

新建Java Library:router_annotation,自定义注解Router:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在

public @interface Router {

// 详细路由路径,如:"MainActivity"

String path();

// 路由组名,如:"app"

String group();

}

新建注解处理器抹开Java Library:router_compiler,在build.gradle中需要导入三个库

谷歌提供的处理注解的服务库

JavaPoet,用于自动生成代码的库

我们自定义的注解模块

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,处理@Router注解

implementation project(':router_annotation')

}

// java控制台输出中文乱码

tasks.withType(JavaCompile) {

options.encoding = "UTF-8"

}

sourceCompatibility = "7"

targetCompatibility = "7"

新建Router注解的处理类RouterProcessor,继承自AbstractProcessor:

// AutoService则是固定的写法,加个注解即可

@AutoService(Processor.class)

// 需要处理的注解

@SupportedAnnotationTypes({"com.yu.router_annotation.Router"})

// 指定JDK编译版本

@SupportedSourceVersion(SourceVersion.RELEASE_7)

// 注解处理器接收的参数

@SupportedOptions({"moduleName", "packageNameForAPT"})

public class RouterProcessor extends AbstractProcessor {

// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

...

}

// 处理注解的函数

@Override

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

...

return false;

}

}

这里的注解@ SupportedOptions要说明一下,这个是该处理器接收的参数,moduleName是模块名,packageNameForAPT是APT生成的文件存放包名,这个参数是在各模块的build.gradle中传递过来的,一会再看,先看init里需要初始化的工作,init的参数ProcessingEnvironment中可以获取一些工具类来处理注解:

// 操作Element工具类 (类、函数、属性都是Element)

Elements elementUtils = processingEnvironment.getElementUtils();

// type(类信息)工具类,包含用于操作TypeMirror的工具方法

Types typeUtils = processingEnvironment.getTypeUtils();

// Messager用来报告错误,警告和其他提示信息

Messager messager = processingEnvironment.getMessager();

// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件

Filer filer = processingEnvironment.getFiler();

还需要通过ProcessingEnvironment把moduleName和packageNameForAPT两个参数获也取出来:

Map options = processingEnvironment.getOptions();

moduleName = options.get("moduleName");

packageNameForAPT = options.get("packageNameForAPT");

然后就是process函数了,这里就是需要使用APT的工具,按照JavaPoet的规则去生成我们想要的类了,JavaPoet使用方法,看这里。

处理过程:

// 处理注解的函数

@Override

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

// 一旦有类之上使用@Router注解

if (!EmptyUtils.isEmpty(set)) {

// 获取所有被 @Router 注解的 元素集合

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

if (!EmptyUtils.isEmpty(elements)) {

// 解析元素

try {

parseElements(elements);

return true;

} catch (IOException e) {

e.printStackTrace();

}

}

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

return true;

}

return false;

}

private void parseElements(Set extends Element> elements) throws IOException {

TypeName methodReturns = ParameterizedTypeName.get(

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

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

ClassName.get(Class.class) // Map

);

MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder("loadPath") // 方法名

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

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

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

// 初始化Map:Map pathMap = new HashMap<>();

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

ClassName.get(Map.class),

ClassName.get(String.class),

ClassName.get(Class.class),

"pathMap",

HashMap.class);

// 便利Activity,存入Map

for (Element element : elements) {

Router router = element.getAnnotation(Router.class);

methodBuidler.addStatement(

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

"pathMap",

router.path(), // "/app/MainActivity"

ClassName.get((TypeElement) element) // MainActivity.class

);

}

methodBuidler.addStatement("return $N", "pathMap");

// 最终生成的类文件名

String finalClassName = "router$$Path$$" + moduleName;

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

packageNameForAPT + "." + finalClassName);

// 生成类文件:Router$$Path$$app

JavaFile.builder(packageNameForAPT, // 包名

TypeSpec.classBuilder(finalClassName) // 类名

.addSuperinterface(ClassName.get(elementUtils.getTypeElement("com.yu.router_api.RouterLoadPath"))) // 实现RouterLoadPath接口

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

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

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

.build() // JavaFile构建完成

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

}

然后看下上看提到的两个参数,模块名和生成文件的包名,是在各模块的build.gradle中传递过来的,packageNameForAPT在公共的config.gradle中定义:

// 包名,用于存放APT生成的类文件

packageNameForAPT = "com.yu.modular.apt"

各模块的build.gradle中添加:

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

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

javaCompileOptions {

annotationProcessorOptions {

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

}

}

生成的文件在对应模块的这个目录下,当然包名是自己定义的:

d70dac036dd5

APT生成的文件路径

到这里APT自动生成的文件也搞完了,但是这里APT生成的Router\$\$Path\$\$模块名的文件是在各自的模块中,在模块不互相引用的情况下不能直接获取,用Class.forName()去取当然没问题,这里在公共库里用一个Manager去管理,做了缓存和懒加载优化:

public class RouterManager {

private static final String TAG = "RouterManager";

private static RouterManager instance;

//Router$$Path$$xxx 类的缓存,防止每次调用Class.forName()、newInstance()

//结构如:

// {

// app : Router$$Path$$app对象,

// order : Router$$Path$$order对象

// ...

// }

private LruCache routerClassCache;

//目标类缓存,目前为 Activity

//结构如:

// {

// app/MainActivity : com.yu.modular.app.MainActivity.class,

// order/Order_MainActivity : com.yu.modular.order.Order_MainActivity.class

// ...

// }

private LruCache targetClassCache;

private RouterManager() {

routerClassCache = new LruCache<>(66);

targetClassCache = new LruCache<>(66);

}

/**

* 单例

*

* @return

*/

public static RouterManager getInstance() {

if (instance == null) {

synchronized (RouterManager.class) {

if (instance == null) {

instance = new RouterManager();

}

}

}

return instance;

}

/**

* 获取目标类

*

* @param groupName

* @param pathName

* @return

*/

public Class get(String groupName, String pathName) {

Class targetClass = targetClassCache.get(groupName + "/" + pathName);

if (targetClass == null) {

RouterLoadPath routerPathClass = getRouterPathClass(groupName);

Map pathMap = routerPathClass.loadPath();

targetClass = pathMap.get(pathName);

Log.e(TAG, "新建目标类文件:" + pathName);

targetClassCache.put(groupName + "/" + pathName, targetClass);

}

return targetClass;

}

/**

* 获取APT生成的类

*

* @param groupName

* @return

*/

public RouterLoadPath getRouterPathClass(String groupName) {

try {

RouterLoadPath routerLoadPath = routerClassCache.get(groupName);

if (routerLoadPath == null) {

String groupClassName = "com.yu.modular.apt.Router$$Path$$" + groupName;

routerLoadPath = (RouterLoadPath) Class.forName(groupClassName).newInstance();

Log.e(TAG, "新建RouterPath文件:" + groupName);

routerClassCache.put("groupName", routerLoadPath);

}

return routerLoadPath;

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

}

可以愉快的跳转了了:

public void jumpOrder(View view) {

Class clazz = RouterManager.getInstance().get("order", "Order_MainActivity");

Intent intent = new Intent(this, clazz);

startActivity(intent);

}

还有一个问题,如果想跨模块访问数据怎么搞?

比如想要在shoppingCar模块中展示Order模块中的图片。当然这些图片资源可以放在公共库,假设这站图片就是属于shoppingCar业务的,只有Order要用一下,其他模块并不需要(也不要太较真,就是用图片做个例子,实现方式同样适用于传递Bean)。这个跟传递Activity原理是一样的,先看实现再分析为什么这么做。

首先在公共库中定义一个接口:

/**

* 订单模块对外暴露接口,其他模块可以获取返回res资源

* 具体的实现在Order模块中

*/

public interface OrderDrawable {

int getDrawable();

}

然后在Order模块中实现,并使用@Router注解:

@Router(group = "order", path = "OrderDrawable")

public class OrderDrawableImpl implements OrderDrawable {

@Override

public int getDrawable() {

return R.mipmap.order_wtf;

}

}

RouterManager封装一下:

/**

* 获取目标类对象

*

* @param groupName

* @param pathName

* @return

*/

public Object getResource(String groupName, String pathName) {

try {

// 调用获取Activity类的方法,返回newInstance()

return this.get(groupName, pathName).newInstance();

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

使用:

OrderDrawable orderDrawable = (OrderDrawable) RouterManager.getInstance().getResource("order", "OrderDrawable");

imageView.setImageResource(orderDrawable.getDrawable());

和Activity不一样的只有在公共库中新建了一个接口,为什么要创建这个接口?

因为两个模块是独立的,即使通过Class.forName("...").newInstance()获取到这个类,也不知道这个类有getDrawable()方法,所以需要公共库里的这个接口。

完事,只是一个实现的简单思路,很多不严谨的地方,比如判空、判断注解格式等等等等,需要封装抽取、优化、拓展还有很多很多空间。

项目地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值