赞
踩
这里主要介绍AOP编程之javapoet框架,javapoet框架也叫注解处理框架,必须依赖于注解;
1、新建两个javaModule(可以先建两个正常的android Library项目),一个用来存放注解,一个用来存放注解处理器,如下图:
需要注意的是这里要把android Library改成java,所以要把两个Module的gradle配置文件改成如下配置:
- apply plugin: 'java'
- dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- }
上面说到,这两个项目,一个用来存放注解和一些其它的类,一个用来存放处理注解的类,
在处理注解类的Module中,需要在dependencies添加一下依赖,如下:
- dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation project(':aptnote')
- implementation 'com.squareup:javapoet:1.9.0'
- implementation 'com.google.auto.service:auto-service:1.0-rc3'
- }
这里添加的依赖主要用来处理注解的,两个项目建好之后,准备工作就完成了;
这里将以两个示例来说明,看示例之前,你最好看一下基本语法
1、这里先模仿一个很简单的Butterknife的实例:
新建注解
- @Retention(RetentionPolicy.CLASS)
- @Target(ElementType.FIELD)
- public @interface BindFieldView {
- int id();
- }
注解中有id属性,用来存放控件id,接下来创建注解处理类
- @AutoService(Processor.class)
- public class BindFieldViewProcessor extends AbstractProcessor {
-
-
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- Set<String> types = new LinkedHashSet<>();
- // 添加了关注的注解
- types.add(BindFieldView.class.getCanonicalName());
- return types;
- }
-
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.latestSupported();
- }
-
-
- @Override
- public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
- Map<String, BindFieldViewCollection> map = new HashMap<>();
-
- for (Element element : roundEnvironment.getElementsAnnotatedWith(BindFieldView.class)) {
- // 获取注解类所在的类元素
- TypeElement classElement = (TypeElement) element.getEnclosingElement();
- // 定义key
- String key = classElement.getQualifiedName().toString();
- if (!map.containsKey(key)) {
- map.put(key, new BindFieldViewCollection(classElement));
- }
- // 获取信息处理类
- BindFieldViewCollection mBindFieldViewCollection = map.get(key);
- // 添加被注解的成员变量元素
- mBindFieldViewCollection.addBindFieldView((VariableElement) element);
- }
- for (Map.Entry<String, BindFieldViewCollection> item : map.entrySet()) {
- // 按类处理注解
- item.getValue().play(processingEnv);
- }
- if (map.size() > 0) {
- // 添加统一管理类BindFieldViewService
- new BindFieldViewServiceCollection(map).play(processingEnv);
- }
-
- return true;
- }
- }

这里新建了一个类继承了AbstractProcessor类,主要就是添加了关注的注解,然后在process方法中就可以开始创作了;
说一下简单原理:
1、为每个Activity自动生成了一个以activity类名加上“$$Bind”结尾的类名的控件绑定类,创建该类的实例并调用init()方法就可以完成控件的赋值;
2、把第一部分创建的所有的类,用一个管理类,管理起来;
3、创建一个工具,调用管理类的bind()方法,既可以获取第一步创建的类,继而完成初始化
4、在每个activity的onCreate()方法中调用BindFieldViewUtil.getInstance().bind(this)即可
这里贴出类的创建关键代码: 创建每一个activity的控件绑定类
public void play(ProcessingEnvironment processingEnvironment) { // 生成方法 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("init") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .returns(TypeName.VOID); // 添加目标activity methodBuilder.addStatement("$T targetActivity = ($T)activity", mClassElement, mClassElement); for (VariableElement item : mFieldElements) { // 获取注解成员变量的注解 BindFieldView mBindFieldView = item.getAnnotation(BindFieldView.class); // 获取控件ID int id = mBindFieldView.id(); // 添加方法内容 methodBuilder.addStatement("targetActivity.$N = targetActivity.findViewById($L)", item.getSimpleName(), id); } // 生成构造函数 MethodSpec constructors = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(ClassName.get("android.app", "Activity"), "activity") .addStatement("this.$N = $N", "activity", "activity") .build(); // 生成成员变量 FieldSpec fieldSpec = FieldSpec.builder(ClassName.get("android.app", "Activity"), "activity") .addModifiers(Modifier.PRIVATE) .build(); // 生成类 TypeSpec finderClass = TypeSpec.classBuilder(mClassElement.getSimpleName() + "$$Bind") .addModifiers(Modifier.PUBLIC) .addSuperinterface(ClassName.get("com.alfredxl.aptdemo.butterknife", "IBind")) .addField(fieldSpec) .addMethod(constructors) .addMethod(methodBuilder.build()) .build(); try { JavaFile.builder(ElementUtils.getPackageName(mClassElement), finderClass).build().writeTo(processingEnvironment.getFiler()); } catch (IOException e) { e.printStackTrace(); } }
创建统一管理类:
public void play(ProcessingEnvironment processingEnvironment) { // 生成方法 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .returns(ClassName.get("com.alfredxl.aptdemo.butterknife", "IBind")) .addParameter(ClassName.get("android.app", "Activity"), "activity"); // 生成方法体 methodBuilder.addStatement("$T className = activity.getClass().getName()", String.class); // 方法体代码块 CodeBlock.Builder caseBlock = CodeBlock.builder().beginControlFlow("switch (className)"); for (Map.Entry<String, BindFieldViewCollection> item : map.entrySet()){ // 注解所在的类 TypeElement classTypeElement =item.getValue().getClassElement(); caseBlock.add("case $S:\n", classTypeElement.getQualifiedName()).indent() .addStatement("return new $T(activity)", ClassName.get(ElementUtils.getPackageName(classTypeElement), classTypeElement.getSimpleName().toString() + "$$Bind")).unindent(); } caseBlock.add("default:\n").indent().addStatement("return null").unindent(); caseBlock.endControlFlow(); methodBuilder.addCode(caseBlock.build()); // 生成类 TypeSpec finderClass = TypeSpec.classBuilder("BindFieldViewService") .addModifiers(Modifier.PUBLIC) .addSuperinterface(ClassName.get("com.alfredxl.aptdemo.butterknife", "IBindFieldViewService")) .addMethod(methodBuilder.build()) .build(); try { JavaFile.builder("com.alfredxl.aptdemo.butterknife", finderClass).build().writeTo(processingEnvironment.getFiler()); } catch (IOException e) { e.printStackTrace(); } }
apt代码也算是比较容易写了,这里对语法部分就不多做讲解了,直接上生成的效果图:
在这个目录下面生成的就是用apt自动生成的代码,其中以$$Bind结尾的就是上面说的每个Activity的控件绑定类;
- public class MainActivity$$Bind implements IBind {
- private Activity activity;
-
- public MainActivity$$Bind(Activity activity) {
- this.activity = activity;
- }
-
- @Override
- public void init() {
- MainActivity targetActivity = (MainActivity)activity;
- targetActivity.mTextView = targetActivity.findViewById(2131165315);
- targetActivity.mImageView = targetActivity.findViewById(2131165247);
- }
- }
上面就是自动生成的代码了。简单的butterknife注解框架就完成了,当然要想真正使用,这点是不够的,还需继续完善;
2、这里再模仿一个很简单的ARouter的实例:
做过Android开发的都知道什么是模块化,随着APP越来越大,功能模块越来越多,也相对独立, 我们就需要独立模块出去了,
以便于项目管理,这之中模块之间的activity相互调用,就是一个大问题了,但是运用APT技术,我们可以解决这个问题,
ARouter框架也就由此而来,其实说到这个框架,原理也是很简单的,那么下面就来实际动手;
同样先建立注解:
- @Retention(RetentionPolicy.CLASS)
- @Target(ElementType.TYPE)
- public @interface ArouterPath {
- String path();
- }
这个注解是只能出现在类上的,所以我们要注意Target类型,注解中有path,用来对应相关activity, 接下来添加注解处理器:
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> mARouterSet = roundEnvironment.getElementsAnnotatedWith(ArouterPath.class); if (mARouterSet != null && mARouterSet.size() > 0) { ARouterCollection mARouterCollection = new ARouterCollection(); for (Element element : mARouterSet) { if (element instanceof TypeElement) { // 添加被注解的类元素 mARouterCollection.addTypeElement((TypeElement) element); } } try { mARouterCollection.play(processingEnv); } catch (ClassNotFoundException e) { System.out.println(e.getMessage()); e.printStackTrace(); } } return true; }
这里贴出主要代码,说下原理;
1、创建一个统一管理路径类,使用Map集合,管理path和Actvity的对应关系;
2、工具类,调用管理类,根据Path既可查询到相关的Actvity,并启动它;
接下来就是管理类的创建代码了:
public void play(ProcessingEnvironment processingEnvironment) throws ClassNotFoundException { // 生成方法 MethodSpec method = MethodSpec.methodBuilder("getActivityClass") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .addParameter(String.class, "path") .returns(Class.class) .addStatement("return map.get(path)") .build(); // 生成构造函数 MethodSpec.Builder constructors = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addStatement("map = new $T()", HashMap.class); for (TypeElement item : mClassElements) { // 获取注解成员变量的注解 ArouterPath mArouterPath = item.getAnnotation(ArouterPath.class); // 获取控件ID String path = mArouterPath.path(); // 添加方法内容 constructors.addStatement("map.put($S, $T.class)", path, item); } // 生成成员变量 FieldSpec fieldSpec = FieldSpec.builder(ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(Class.class)), "map") .addModifiers(Modifier.PRIVATE) .build(); // 生成类 TypeSpec finderClass = TypeSpec.classBuilder("ARouterService") .addModifiers(Modifier.PUBLIC) .addSuperinterface(ClassName.get("com.alfredxl.aptdemo.arouter", "IARouterService")) .addField(fieldSpec) .addMethod(constructors.build()) .addMethod(method) .build(); try { JavaFile.builder("com.alfredxl.aptdemo", finderClass).build().writeTo(processingEnvironment.getFiler()); } catch (IOException e) { e.printStackTrace(); } }
生成逻辑也是相当的简单,效果如下:
- public class ARouterService implements IARouterService {
- private Map<String, Class> map;
-
- public ARouterService() {
- map = new HashMap();
- map.put("Activity2", Activity2.class);
- }
-
- @Override
- public Class getActivityClass(String path) {
- return map.get(path);
- }
- }
总体来说,aptnote框架确实非常强大,特别是在代码书写这一块,比javassist要好用得多,不过apt也有它的缺陷, 它只能生成新的类,不能修改原有类(也是遵守了AOP,不对源码进行修改);
说到这里,如果想对源码进行修改,又不在我们的代码中留下痕迹,就只能修改编译后的class文件了,
而这里,修改class文件,封装的还算好用的就是javassist了,有兴趣可以关注javassist示例;
源码地址:项目源码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。