利用注解和APT生成模板代码

开题

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

注解的核心方法

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

@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. 获取注解 如 :
    Test.class.getAnnotation(ViewHolder.class)
    或者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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值