当前位置:   article > 正文

Android AOP 之 javapoet (APT)示例_javapoet getinstance

javapoet getinstance

APTDemo

这里主要介绍AOP编程之javapoet框架,javapoet框架也叫注解处理框架,必须依赖于注解;

一、准备工作:

1、新建两个javaModule(可以先建两个正常的android Library项目),一个用来存放注解,一个用来存放注解处理器,如下图:

项目结构
需要注意的是这里要把android Library改成java,所以要把两个Module的gradle配置文件改成如下配置:

  1. apply plugin: 'java'
  2. dependencies {
  3. implementation fileTree(dir: 'libs', include: ['*.jar'])
  4. }

上面说到,这两个项目,一个用来存放注解和一些其它的类,一个用来存放处理注解的类,
在处理注解类的Module中,需要在dependencies添加一下依赖,如下:

  1. dependencies {
  2. implementation fileTree(dir: 'libs', include: ['*.jar'])
  3. implementation project(':aptnote')
  4. implementation 'com.squareup:javapoet:1.9.0'
  5. implementation 'com.google.auto.service:auto-service:1.0-rc3'
  6. }

这里添加的依赖主要用来处理注解的,两个项目建好之后,准备工作就完成了;

一、开始创作艺术:

这里将以两个示例来说明,看示例之前,你最好看一下基本语法

1、这里先模仿一个很简单的Butterknife的实例:

新建注解

  1. @Retention(RetentionPolicy.CLASS)
  2. @Target(ElementType.FIELD)
  3. public @interface BindFieldView {
  4. int id();
  5. }

注解中有id属性,用来存放控件id,接下来创建注解处理类

  1. @AutoService(Processor.class)
  2. public class BindFieldViewProcessor extends AbstractProcessor {
  3. @Override
  4. public Set<String> getSupportedAnnotationTypes() {
  5. Set<String> types = new LinkedHashSet<>();
  6. // 添加了关注的注解
  7. types.add(BindFieldView.class.getCanonicalName());
  8. return types;
  9. }
  10. @Override
  11. public SourceVersion getSupportedSourceVersion() {
  12. return SourceVersion.latestSupported();
  13. }
  14. @Override
  15. public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  16. Map<String, BindFieldViewCollection> map = new HashMap<>();
  17. for (Element element : roundEnvironment.getElementsAnnotatedWith(BindFieldView.class)) {
  18. // 获取注解类所在的类元素
  19. TypeElement classElement = (TypeElement) element.getEnclosingElement();
  20. // 定义key
  21. String key = classElement.getQualifiedName().toString();
  22. if (!map.containsKey(key)) {
  23. map.put(key, new BindFieldViewCollection(classElement));
  24. }
  25. // 获取信息处理类
  26. BindFieldViewCollection mBindFieldViewCollection = map.get(key);
  27. // 添加被注解的成员变量元素
  28. mBindFieldViewCollection.addBindFieldView((VariableElement) element);
  29. }
  30. for (Map.Entry<String, BindFieldViewCollection> item : map.entrySet()) {
  31. // 按类处理注解
  32. item.getValue().play(processingEnv);
  33. }
  34. if (map.size() > 0) {
  35. // 添加统一管理类BindFieldViewService
  36. new BindFieldViewServiceCollection(map).play(processingEnv);
  37. }
  38. return true;
  39. }
  40. }

这里新建了一个类继承了AbstractProcessor类,主要就是添加了关注的注解,然后在process方法中就可以开始创作了;
说一下简单原理:
1、为每个Activity自动生成了一个以activity类名加上“$$Bind”结尾的类名的控件绑定类,创建该类的实例并调用init()方法就可以完成控件的赋值;
2、把第一部分创建的所有的类,用一个管理类,管理起来;
3、创建一个工具,调用管理类的bind()方法,既可以获取第一步创建的类,继而完成初始化
4、在每个activity的onCreate()方法中调用BindFieldViewUtil.getInstance().bind(this)即可
这里贴出类的创建关键代码: 创建每一个activity的控件绑定类

  1. public void play(ProcessingEnvironment processingEnvironment) {
  2. // 生成方法
  3. MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("init")
  4. .addAnnotation(Override.class)
  5. .addModifiers(Modifier.PUBLIC)
  6. .returns(TypeName.VOID);
  7. // 添加目标activity
  8. methodBuilder.addStatement("$T targetActivity = ($T)activity", mClassElement, mClassElement);
  9. for (VariableElement item : mFieldElements) {
  10. // 获取注解成员变量的注解
  11. BindFieldView mBindFieldView = item.getAnnotation(BindFieldView.class);
  12. // 获取控件ID
  13. int id = mBindFieldView.id();
  14. // 添加方法内容
  15. methodBuilder.addStatement("targetActivity.$N = targetActivity.findViewById($L)", item.getSimpleName(), id);
  16. }
  17. // 生成构造函数
  18. MethodSpec constructors = MethodSpec.constructorBuilder()
  19. .addModifiers(Modifier.PUBLIC)
  20. .addParameter(ClassName.get("android.app", "Activity"), "activity")
  21. .addStatement("this.$N = $N", "activity", "activity")
  22. .build();
  23. // 生成成员变量
  24. FieldSpec fieldSpec = FieldSpec.builder(ClassName.get("android.app", "Activity"), "activity")
  25. .addModifiers(Modifier.PRIVATE)
  26. .build();
  27. // 生成类
  28. TypeSpec finderClass = TypeSpec.classBuilder(mClassElement.getSimpleName() + "$$Bind")
  29. .addModifiers(Modifier.PUBLIC)
  30. .addSuperinterface(ClassName.get("com.alfredxl.aptdemo.butterknife", "IBind"))
  31. .addField(fieldSpec)
  32. .addMethod(constructors)
  33. .addMethod(methodBuilder.build())
  34. .build();
  35. try {
  36. JavaFile.builder(ElementUtils.getPackageName(mClassElement), finderClass).build().writeTo(processingEnvironment.getFiler());
  37. } catch (IOException e) {
  38. e.printStackTrace();
  39. }
  40. }

创建统一管理类:

  1. public void play(ProcessingEnvironment processingEnvironment) {
  2. // 生成方法
  3. MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
  4. .addAnnotation(Override.class)
  5. .addModifiers(Modifier.PUBLIC)
  6. .returns(ClassName.get("com.alfredxl.aptdemo.butterknife", "IBind"))
  7. .addParameter(ClassName.get("android.app", "Activity"), "activity");
  8. // 生成方法体
  9. methodBuilder.addStatement("$T className = activity.getClass().getName()", String.class);
  10. // 方法体代码块
  11. CodeBlock.Builder caseBlock = CodeBlock.builder().beginControlFlow("switch (className)");
  12. for (Map.Entry<String, BindFieldViewCollection> item : map.entrySet()){
  13. // 注解所在的类
  14. TypeElement classTypeElement =item.getValue().getClassElement();
  15. caseBlock.add("case $S:\n", classTypeElement.getQualifiedName()).indent()
  16. .addStatement("return new $T(activity)",
  17. ClassName.get(ElementUtils.getPackageName(classTypeElement),
  18. classTypeElement.getSimpleName().toString() + "$$Bind")).unindent();
  19. }
  20. caseBlock.add("default:\n").indent().addStatement("return null").unindent();
  21. caseBlock.endControlFlow();
  22. methodBuilder.addCode(caseBlock.build());
  23. // 生成类
  24. TypeSpec finderClass = TypeSpec.classBuilder("BindFieldViewService")
  25. .addModifiers(Modifier.PUBLIC)
  26. .addSuperinterface(ClassName.get("com.alfredxl.aptdemo.butterknife", "IBindFieldViewService"))
  27. .addMethod(methodBuilder.build())
  28. .build();
  29. try {
  30. JavaFile.builder("com.alfredxl.aptdemo.butterknife", finderClass).build().writeTo(processingEnvironment.getFiler());
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. }

apt代码也算是比较容易写了,这里对语法部分就不多做讲解了,直接上生成的效果图:

目录结构

在这个目录下面生成的就是用apt自动生成的代码,其中以$$Bind结尾的就是上面说的每个Activity的控件绑定类;

  1. public class MainActivity$$Bind implements IBind {
  2. private Activity activity;
  3. public MainActivity$$Bind(Activity activity) {
  4. this.activity = activity;
  5. }
  6. @Override
  7. public void init() {
  8. MainActivity targetActivity = (MainActivity)activity;
  9. targetActivity.mTextView = targetActivity.findViewById(2131165315);
  10. targetActivity.mImageView = targetActivity.findViewById(2131165247);
  11. }
  12. }

上面就是自动生成的代码了。简单的butterknife注解框架就完成了,当然要想真正使用,这点是不够的,还需继续完善;

2、这里再模仿一个很简单的ARouter的实例:

做过Android开发的都知道什么是模块化,随着APP越来越大,功能模块越来越多,也相对独立, 我们就需要独立模块出去了,
以便于项目管理,这之中模块之间的activity相互调用,就是一个大问题了,但是运用APT技术,我们可以解决这个问题,
ARouter框架也就由此而来,其实说到这个框架,原理也是很简单的,那么下面就来实际动手;
同样先建立注解:

  1. @Retention(RetentionPolicy.CLASS)
  2. @Target(ElementType.TYPE)
  3. public @interface ArouterPath {
  4. String path();
  5. }

这个注解是只能出现在类上的,所以我们要注意Target类型,注解中有path,用来对应相关activity, 接下来添加注解处理器:

  1. @Override
  2. public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  3. Set<? extends Element> mARouterSet = roundEnvironment.getElementsAnnotatedWith(ArouterPath.class);
  4. if (mARouterSet != null && mARouterSet.size() > 0) {
  5. ARouterCollection mARouterCollection = new ARouterCollection();
  6. for (Element element : mARouterSet) {
  7. if (element instanceof TypeElement) {
  8. // 添加被注解的类元素
  9. mARouterCollection.addTypeElement((TypeElement) element);
  10. }
  11. }
  12. try {
  13. mARouterCollection.play(processingEnv);
  14. } catch (ClassNotFoundException e) {
  15. System.out.println(e.getMessage());
  16. e.printStackTrace();
  17. }
  18. }
  19. return true;
  20. }

这里贴出主要代码,说下原理;
1、创建一个统一管理路径类,使用Map集合,管理path和Actvity的对应关系;
2、工具类,调用管理类,根据Path既可查询到相关的Actvity,并启动它;
接下来就是管理类的创建代码了:

  1. public void play(ProcessingEnvironment processingEnvironment) throws ClassNotFoundException {
  2. // 生成方法
  3. MethodSpec method = MethodSpec.methodBuilder("getActivityClass")
  4. .addModifiers(Modifier.PUBLIC)
  5. .addAnnotation(Override.class)
  6. .addParameter(String.class, "path")
  7. .returns(Class.class)
  8. .addStatement("return map.get(path)")
  9. .build();
  10. // 生成构造函数
  11. MethodSpec.Builder constructors = MethodSpec.constructorBuilder()
  12. .addModifiers(Modifier.PUBLIC)
  13. .addStatement("map = new $T()", HashMap.class);
  14. for (TypeElement item : mClassElements) {
  15. // 获取注解成员变量的注解
  16. ArouterPath mArouterPath = item.getAnnotation(ArouterPath.class);
  17. // 获取控件ID
  18. String path = mArouterPath.path();
  19. // 添加方法内容
  20. constructors.addStatement("map.put($S, $T.class)", path, item);
  21. }
  22. // 生成成员变量
  23. FieldSpec fieldSpec = FieldSpec.builder(ParameterizedTypeName.get(
  24. ClassName.get(Map.class),
  25. ClassName.get(String.class),
  26. ClassName.get(Class.class)), "map")
  27. .addModifiers(Modifier.PRIVATE)
  28. .build();
  29. // 生成类
  30. TypeSpec finderClass = TypeSpec.classBuilder("ARouterService")
  31. .addModifiers(Modifier.PUBLIC)
  32. .addSuperinterface(ClassName.get("com.alfredxl.aptdemo.arouter", "IARouterService"))
  33. .addField(fieldSpec)
  34. .addMethod(constructors.build())
  35. .addMethod(method)
  36. .build();
  37. try {
  38. JavaFile.builder("com.alfredxl.aptdemo",
  39. finderClass).build().writeTo(processingEnvironment.getFiler());
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43. }

生成逻辑也是相当的简单,效果如下:

  1. public class ARouterService implements IARouterService {
  2. private Map<String, Class> map;
  3. public ARouterService() {
  4. map = new HashMap();
  5. map.put("Activity2", Activity2.class);
  6. }
  7. @Override
  8. public Class getActivityClass(String path) {
  9. return map.get(path);
  10. }
  11. }

一、总结:

总体来说,aptnote框架确实非常强大,特别是在代码书写这一块,比javassist要好用得多,不过apt也有它的缺陷, 它只能生成新的类,不能修改原有类(也是遵守了AOP,不对源码进行修改);
说到这里,如果想对源码进行修改,又不在我们的代码中留下痕迹,就只能修改编译后的class文件了,
而这里,修改class文件,封装的还算好用的就是javassist了,有兴趣可以关注javassist示例

源码地址:项目源码

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/240093
推荐阅读
相关标签
  

闽ICP备14008679号