android 注解限制参数,Android注解,这几篇文章就够了(三)自己写个注解处理器...

一 前言

前面两篇文章,注解处理器,理解注解,对注解有了一个初步认识,第二篇文章末尾也提到了,注解不是代码的一部分,当开发者使用了Annotation注解以后,注解不会自己起作用,必须提供相应的代码来处理这些信息。

这篇文章,我们就写一个简单的注解处理器,作用是类似于ButterKnife查找id。

源码传送门

二 项目结构

整个项目采用如下所示的结构:

8e46db1e0c96

BindViewAnnotation,Java Library,存放我们定义的注解。

bindviewapi,Android Library,声明注解框架使用的api,本例子中,我们要实现的是查找view控件,并将控件和xml中的绑定。

BindViewCompiler,Java Library,注解处理器,根据注解生成处理打代码。

新建这几个Library的过程不再陈述,特别注意的是,建BindViewCompiler Java Library时,在build.gradle下要加入以下代码:

// 用于生成Java文件的库

implementation 'com.squareup:javapoet:1.11.1'

implementation 'com.google.auto.service:auto-service:1.0-rc6'

annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

这些代码后面会提及。

三 正式开始吧

(一)在BindViewAnnotation新建注解

前面说过了,我们要实现的功能是查找xml文件的id功能,注解歹意如下:

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.CLASS)

public @interface BindView {

int value();

}

使用注解,在Activity中使用注解,在xml中定义一个button,

android:id="@+id/btn_bind"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="BindButton"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toTopOf="parent" />

在Activity中使用我们定义的注解。

@BindView(R.id.btn_bind)

public Button mButton;

前面说了,注解不会自动起作用,如果我们直接运行代码,会直接报错的,提示Button没有定义,所以我们要写代码来处理这个注解的信息。

(二) 声明注解框架用到的api

① 定义一个绑定注解的接口

public interface IViewBind {

void bind(T t);

}

② 向外提供的绑定方法,这里使用静态方法来管理。

public class ViewBinder {

public static void bind(Activity activity){

try {

// 1

Class clazz=Class.forName(activity.getClass().getCanonicalName()+"$$ViewBinder");

// 2

IViewBind iViewBinder= (IViewBind) clazz.newInstance();

// 3

iViewBinder.bind(activity);

} catch (ClassNotFoundException e) {

Log.d("hbj--exp",e.getException()+"");

Log.d("hbj--cause",e.getCause()+"");

Log.d("hbj--mess",e.getMessage()+"");

Log.d("hbj--class",e.getClass()+"");

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

}

}

}

// 1 处获取生成的类的名字,生成类的规则代码在后面写,生成的规则是 类名$$ViewBinder,例如在MainActivity中需要使用,生成的文件名字就是MainActivity$$ViewBinder.java。

// 2 获取生成的类的实例。

// 3 完成绑定。

可能对第二条和第三条不是很好理解,现在贴出生成的java文件的源码,结合生成的文件,应该就好理解了吧。

public class MainActivity$$ViewBinder< T extends MainActivity> implements IViewBind {

@Override

public void bind(T activity) {

activity.mButton=activity.findViewById(2131165250);

}

}

(三) 根据注解生成代码

现在只剩根据注解生成代码。

创建一个自定义的Annotation Processor,继承自AbstractProcessor。

// 1

@AutoService(Processor.class)

public class MyProcessor extends AbstractProcessor {

// 2

@Override

public synchronized void init(ProcessingEnvironment env){

}

// 3

@Override

public boolean process(Set extends TypeElement> annoations, RoundEnvironment roundEnv) { }

// 4

@Override

public Set getSupportedAnnotationTypes() {

}

// 5

@Override

public SourceVersion getSupportedSourceVersion() {

}

}

// 1处 @AutoService(Processor.class),向javac注册我们自定义的注解处理器, 这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。

AutoService主要是生成 META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解的话,需要通过以下方法进行手动配置进行手动注册:

1.在main下创建resources目录,然后创建META-INF/services 目录,最后在此目录下创建文件:javax.annotation.processing.Processor,目录如下,

8e46db1e0c96

文件里的内容是一些列的自定义注解处理器完整有效的类名集合,以换行符切割,这里就自定义了一个注解处理器,

8e46db1e0c96

注:但是有可能会出现使用@AutoService()无法动态生成入口文件的,这个问题可以如下解决:

这个要从google auto service 和META_INF,谷歌的 auto service 也是一种 Annotation Processor,它能自动生成 META-INF 目录以及相关文件,避免手工创建该文件,手工创建的方法,上文有,手工创建有可能失误。使用 auto service 中的 @AutoService(Processor.class) 注解修饰 Annotation Processor 类就可以在编译过程中自动生成文件。

如果要进入的话,还要注意要引入两个配置:

implementation 'com.google.auto.service:auto-service:1.0-rc6'

annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

这两个重复写的原因是:annotationProcessor这个是新版 gradle 提供的 java plugin 内置的配置项,在gradle 5.+ 中将 Annotation Processor 从编译期 classpath 中去除了,javac 也就无法发现 Annotation Processor。此处如果按照 gradle 4.+ 的写法,只写一个 implementation 是无法使用 auto service 的 Annotation Processor 的。必须要使用 annotationProcessor 来配置 Annotation Processor 使其生效。

// 2处 init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。

// 3处 public boolean process(Set extends TypeElement> annoations, RoundEnvironment env)这相当于每个处理器的主函数main()。 在这里写扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让查询出包含特定注解的被注解元素。

// 4处 getSupportedAnnotationTypes(),这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,在这里定义你的注解处理器注册到哪些注解上。

// 5处 getSupportedSourceVersion();用来指定你使用的Java版本。

下面给出BindViewProcessor的完整代码:

@AutoService(Processor.class)

public class BindViewProcessor extends AbstractProcessor {

// 文件相关辅助类

private Filer mFiler;

// 日志相关辅助类

private Messager mMessager;

@Override

public synchronized void init(ProcessingEnvironment processingEnv) {

super.init(processingEnv);

mFiler = processingEnv.getFiler();

mMessager = processingEnv.getMessager();

}

@Override

public SourceVersion getSupportedSourceVersion() {

return SourceVersion.latestSupported();

}

@Override

public Set getSupportedAnnotationTypes() {

Set annotations=new LinkedHashSet<>();

annotations.add(BindView.class.getCanonicalName());

return annotations;

}

@Override

public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {

Set extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

Map> bindViewMap=new HashMap<>();

for (Element element : elements) {

// 因为BindView只作用于Filed,判断注解是否是属性,不是的话直接结束

if (element.getKind() != ElementKind.FIELD) {

mMessager.printMessage(Diagnostic.Kind.ERROR, element.getSimpleName().toString() + " is not filed,can not use @BindView");

return false;

}

// 获取注解元数据

int id = element.getAnnotation(BindView.class).value();

// 获取属性的类

TypeElement typeElement= (TypeElement) element.getEnclosingElement();

if (!bindViewMap.containsKey(typeElement)){

bindViewMap.put(typeElement,new ArrayList());

}

ArrayList bindViewInfos=bindViewMap.get(typeElement);

// 添加list

bindViewInfos.add(new BindViewInfo(id,element.getSimpleName().toString()));

}

produceClass(bindViewMap);

return true;

}

private void produceClass(Map> hasMap){

if (hasMap == null || hasMap.isEmpty()){

return;

}

Set typeElements=hasMap.keySet();

for (TypeElement typeElement:typeElements){

produceJavaClass(typeElement,hasMap.get(typeElement));

}

}

/**

* 产生Java文件

* @param typeElement

* @param bindViewInfos

*/

private void produceJavaClass(TypeElement typeElement, List bindViewInfos){

try {

StringBuffer stringBuffer=new StringBuffer();

stringBuffer.append("package ");

stringBuffer.append(getPackageName(typeElement.getQualifiedName().toString())+";\n");

stringBuffer.append("import com.jackson.bindviewapi.IViewBind;\n");

stringBuffer.append("public class "+typeElement.getSimpleName()+"$$ViewBinder< T extends "+typeElement.getSimpleName()+"> implements IViewBind {\n");

stringBuffer.append("@Override\n");

stringBuffer.append("public void bind(T activity) {\n");

for (BindViewInfo bindViewInfo:bindViewInfos){

stringBuffer.append("activity."+bindViewInfo.name+"=activity.findViewById("+bindViewInfo.id+");\n");

}

stringBuffer.append("}\n}");

JavaFileObject javaFileObject=mFiler.createSourceFile(typeElement.getQualifiedName().toString()+"$$ViewBinder");

Writer writer=javaFileObject.openWriter();

writer.write(stringBuffer.toString());

writer.close();

} catch (IOException e) {

e.printStackTrace();

}

}

private String getPackageName(String className){

if (className==null || className.equals("")){

return "";

}

return className.substring(0,className.lastIndexOf("."));

}

}

重新编译项目,在app/build/source/apt/debug/com.jackson.annotationdemo下就会找到生成的文件,如下图

8e46db1e0c96

生成文件的代码,前面已经给出,可以对照着生成java文件的代码,来看BindViewProcessor生成java文件的代码规则。

(四) 使用

在MainActivity中使用,代码如下:

@BindView(R.id.btn_bind)

public Button mButton;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ViewBinder.bind(this);

mButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Toast.makeText(MainActivity.this,"注解测试",Toast.LENGTH_SHORT).show();

}

});

}

可以看到,在MainActivity中,并没有mButton的findViewById()来初始化,而是通过注解完成,代码运行正常。

源码传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值