Android AOP 之 javapoet (APT)示例

APTDemo

这里主要介绍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示例

源码地址:项目源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值