APT_RecyclerViewHolder

Introduction: 利用注解、APT、javaopet 自动生成 RecyclerViewHolder 代码
More: Author   ReportBugs   
Tags:

利用注解、APT、javaPoet 自动生成 RecyclerViewHolder 代码 自动生成代码 @TOC 具体看博客 https://blog.csdn.net/wzlyd1/article/details/85005840 @TOC

开题

注解在很多框架中频繁的使用,比如大名鼎鼎的 ButterKnife,ButterKnife 运用的就是编译时注解,ButterKnife 在我们编译时,就根据注解,自动生成了一些辅助类。当然还有 EventBus3,也用到了注解的方法。所以,学习注解显得就尤为重要。

注解的核心方法

在介绍核心用法之前,先照例引用一些概念

@Retention 
这个注解表示注解的保留方式,有如下三种: 
SOURCE:只保留在源码中,不保留在 class 中,同时也不加载到虚拟机中 
CLASS:保留在源码中,同时也保留到 class 中,但是不加载到虚拟机中 
RUNING:保留到源码中,同时也保留到 class 中,最后加载到虚拟机中

@Target 
这个注解表示注解的作用范围,主要有如下:
ElementType.FIELD 注解作用于变量
ElementType.METHOD 注解作用于方法
ElementType.PARAMETER 注解作用于参数
ElementType.CONSTRUCTOR 注解作用于构造方法
ElementType.LOCAL_VARIABLE 注解作用于局部变量
ElementType.PACKAGE 注解作用于包
由于该例子只用到这几个元注解,所以只需要了解这两个即可,其他自行查询资料。

在我理解看来,注解的核心使用方法就三个

  1. 自定义注解 ,如
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ViewHolder {
     String layoutPath();
     String ViewHolderName();
     String packageName();
}
  1. 获取注解 如 :

    或者```xxx.getAnnotation(ViewHolder.class)
    
  2. 获取注解值 如: ViewHolder holder = XXX.getAnnotation(ViewHolder.class) ; String layoutPath = holder.layoutPath()

能拿到值,我们的成功之路就走了 80%,只要有值,我们就能干我们想干的事情。

APT

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的 Annotation,使用 Annotation 进行额外的处理。 Annotation 处理器在处理 Annotation 时可以根据源文件中的 Annotation 生成额外的源文件和其它的文件(文件具体内容由 Annotation 处理器的编写者决定),APT 还会编译生成的源文件和原来的源文件,将它们一起生成 class 文件。 在编译初期,我们想利用注解的值做处理的时候,就用到了这个技术,或者工具。

JavaPoet

又是大神 JakeWharton 开源的一款快速代码生成工具,可使用 API 快速生成.java 文件,与 APT 绝配 JavaPoet github 地址

有了这三样工具,我们就可以让计算机帮我们实现一些模板代码或者口水代码,比如 ViewHolder。

为什么要写这么一套代码?

  1. 学习,毕竟注解很重要,APT 也很重要,学无止境
  2. 懒,虽然 butterKnife 可以帮我们很快的绑定 事件以及 view,可是我还是觉得繁琐,我觉得直接写完布局后一键生成所需代码最简单粗暴。当然有 as 插件帮你写 butterKnife。
  3. 还是为了学习,写这套代码我自己都不一定会用,而且很多 AS 插件支持一键生成代码,比如:Android code Generator 。 废话不多说,我们开始 demo 之路。

apt-annotaition

在一个新的 Android 工程新建后,我们在项目上右击,新建 Module,选择 JavaLibrary. 在这里插入图片描述 起名为 apt-annotaition,用来存放我们的自定义注解。 然后在该 library 的 build.gradle 中增加如下代码

apply plugin: 'java-library'
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

apt-processor

重复上一遍操作,新建 javaLibrary ,然后起名为 apt-processor,这个就是我们的核心代码存放库,用来处理我们的注解,生成.java 模板代码的地方。这个 library 要依赖于apt-annotaition build.gradle 文件做如下处理:

apply plugin: 'java-library'
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.squareup:javapoet:1.11.1'
    implementation project(':apt-annotation')

}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

其中implementation 'com.google.auto.service:auto-service:1.0-rc2' 是谷歌爸爸的一个库,引用网上一段说法就是

**介绍下依赖库 auto-service
在使用注解处理器需要先声明,步骤:
1、需要在 processors 库的 main 目录下新建 resources 资源文件夹;
2、在 resources 文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;)
这样声明下来也太麻烦了?这就是用引入 auto-service 的原因。
通过 auto-service 中的@AutoService 可以自动生成 AutoService 注解处理器是 Google 开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的**
 implementation 'com.squareup:javapoet:1.11.1'

是 javaPoet,帮助我们生成.java 文件。 到这里,我们的准备工作基本完工。 但是别忘了 app 的 build.gradle 配置

app 的 build.gradle

配置如下,不用完全相同,主要是标注部分,要依赖于 apt-processor apt-annotaition 在这里插入图片描述 至此,准备工作完毕。

自定义我们的注解 ViewHolder

在定义之前,我们要先想一下我们的需求需要哪些参数,因为涉及到布局文件,所以我肯定需要文件的路径,以便我们解析,除此之外,我还要一个名字参数,用来给 ViewHolder 起名字。 另外,我们还需要包名,这个是后续开发过程中才想到的,因为我们在 findViewById 的时候引入 R 包需要包名。 基本上就这三个参数,想明白了,就动手吧 在apt-annotaition中新建一个 class 文件,代码如下

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ViewHolder {
     String layoutPath();
     String ViewHolderName();
     String packageName();
}

创建核心代码 ViewHolderProcessor

apt-processor中新建类,继承自AbstractProcessor,复写相关方法

public Set<String> getSupportedAnnotationTypes()
支持的注解类型

由于我们只定义了 ViewHolder 类型,这里返回 ViewHolder 类型的 set 集合即可

 public SourceVersion getSupportedSourceVersion()

支持的源码版本,写死return SourceVersion.latestSupported()即可

public synchronized void init(ProcessingEnvironment processingEnvironment)

初始化方法,用来获取一些必要参数,这里只用到了 filer,mMessager 代码如下

@Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        mFiler = processingEnvironment.getFiler();
    }

mFiler 你可以理解为时文件写入 mMessager 可以理解为 logcat;

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

是我们处理注解的核心方法,在这里我们将获取到注解的类型和值。来生成我们需要的模板代码。 具体代码可见源码。

整体架构

写这个标题,我自己都笑了,一个 demo 有啥架构,不过还是贴上来吧,毕竟把 demo 完整介绍一遍也不现实 在这里插入图片描述

遇到的问题

1.如果你想看 mMessger 打印的日志信息,可以在 build 选项卡中选择 在这里插入图片描述 网上说有 message 选项卡,不知道为什么我的没有

  1. R 包无法导入,之前刚写完的时候没问题,但是现在突然出现了,不知道原因,另外,如果生成的模板文件如果包名和你的 app 包名重复,R 的包更是无法导入,原因未知,所以我现在随便写了一个 com 包。

    使用方法

    在我们的实体类中,使用注解,如 demo 中的 TestEntity 代码如下
@ViewHolder(layoutPath ="/Users/liyaodong/android_dev/Study/APT/app/src/main/res/layout/view_card_product.xml",
ViewHolderName = "MyViewHolder",
        packageName = "com.example.androidopt")
public class TestEntity {
    String title;
    String des;
}

这样,我们的 xml 布局将自动解析,并且与实体类进行绑定 并且自动绑定一些常见方法,如 setText(xxx),setImageResoure(xxx), 如果有额外的方法需求,继承类AbsAndroidMethodHandler,重写

@Override
    public String generatorMethod(String varName) {
        return null;
    }

即可,记得把具体类加入到 AndroidMethodMapFactory 工厂方法的 map 中,进行所有 view 方法的统一管理,这个 map 会根据 view 类型自动寻找适合的 methodHandler 类进行方法生成。 然后一键,点击锤子按钮即可 在这里插入图片描述

生成位置在 app--->build--->generated-->source-->apt 下面 在这里插入图片描述

重要类及函数

public abstract class AbsAndroidMethodHandler implements IAndroidMethodHandler

方法生成类,具体方法

@Override
    public String getPackageName() {
        return packageName;
    }

    @Override
    public String getViewClassName() {
        return className;
    }

    @Override
    public String generatorMethod(String varName) {
        return null;
    }

    @Override
    public String generatorFindViewById(String ViewName, String idName) {
        return ViewName + " = itemView.findViewById($T.id." + idName+")";
    }

具体实现类 TextViewMethodHandler ImageViewMethodHandler

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools