android中注解处理器apt用法示例-kb88凯时官网登录

来自:网络
时间:2024-06-09
阅读:
免费资源网,https://freexyz.cn/

apt,annotation processing tool,即注解处理器,是一种用来处理注解的工具,常用在编译时扫描和处理注解,最终生成处理注解逻辑的java文件。apt技术在目前的很多框架上都有着使用,如butterknife,arouter,greendao等框架上都有着apt技术的影子。

apt作用

使用apt可以在编译时来处理编译时注解,生成额外的java文件,有如下效果:

  • 可以达到减少重复代码手工编写的效果。

    如butterknife,我们可以直接使用注解来减少findviewbyid这些代码,只需要通过注解表示是哪个id就够了。

  • 功能封装。将主要的功能逻辑封装起来,只保留注解调用。

  • 相对于使用java反射来处理运行时注解,使用apt有着更加良好的性能。

android基本编译流程

android中的代码编译时需要经过:java——>class ——> dex 流程,代码最终生成dex文件打入到apk包里面。

apt是在编译开始时就介入的,用来处理编译时注解。

aop(aspect oridnted programming)是在编译完成后生成dex文件之前,通过直接修改.class文件的方式,来对代码进行修改或添加逻辑。常用在在代码监控,代码修改,代码分析这些场景。

apt基本使用

基本使用流程主要包括如下几个步骤:

  • 创建自定义注解
  • 创建注解处理器,处理java文件生成逻辑
  • 封装一个供外部调用的api
  • 项目中调用

本次以仿写butterknife绑定view为例,省略findviewbyid的代码,调用代码如下:

public class annotationtestactivity extends appcompatactivity {
    @bindview(r.id.tv_annotation_test1)
    textview tvannotationtest1;
    @bindview(r.id.tv_annotation_test2)
    textview tvannotationtest2;
    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_annotation_test);
        bindviewtools.bind(this);
        tvannotationtest1.settext("测试文本");
        tvannotationtest1.setonclicklistener(new view.onclicklistener() {
            @override
            public void onclick(view v) {
                logger.toast(workdemoapplication.context,"控件1:" r.id.tv_annotation_test1);
            }
        });
        tvannotationtest2.settext("另一个文本");
        tvannotationtest2.setonclicklistener(new view.onclicklistener() {
            @override
            public void onclick(view v) {
                logger.toast(workdemoapplication.context,"控件2:" r.id.tv_annotation_test2);
            }
        });
    }
}

1、 自定义注解

我们新建立一个java lib,命名为annotationtest,用来承载自定义注解,代码如下所示:

@retention(retentionpolicy.class)
@target(elementtype.field)
public @interface bindview {
    int value();
}

2、注解处理器

额外新建立一个java lib,命名为processortest,用来承载注解处理及java文件生成逻辑。

主要包括如下几个步骤:

  • 添加注解处理器
  • 注解处理器注册
  • 添加java代码生成逻辑

需要注意的是,当前的注解处理器lib需要引入注解lib——annotationtest,在当前module的build.gradle文件中配置:

    implementation project(':annotationtest')//依赖刚刚创建的annotation模块

注解处理器

注解处理器代码如下:

public class bindviewprocessor extends abstractprocessor {
    private messager mmessager;
    private elements melementutils;
    private map mproxymap = new hashmap<>();
    @override
    public synchronized void init(processingenvironment processingenv) {
        super.init(processingenv);
        mmessager = processingenv.getmessager();
        melementutils = processingenv.getelementutils();
    }
    @override
    public set getsupportedannotationtypes() {
        hashset supporttypes = new linkedhashset<>();
        supporttypes.add(bindview.class.getcanonicalname());
        return supporttypes;
    }
    @override
    public sourceversion getsupportedsourceversion() {
        return sourceversion.latestsupported();
    }
    @override
    public boolean process(set set, roundenvironment roundenvironment) {
        mmessager.printmessage(diagnostic.kind.note, "processing...");
        mproxymap.clear();
        //得到所有的注解
        set elements = roundenvironment.getelementsannotatedwith(bindview.class);
        for (element element : elements) {
            variableelement variableelement = (variableelement) element;
            typeelement classelement = (typeelement) variableelement.getenclosingelement();
            string fullclassname = classelement.getqualifiedname().tostring();
            classcreatorproxy proxy = mproxymap.get(fullclassname);
            if (proxy == null) {
                proxy = new classcreatorproxy(melementutils, classelement);
                mproxymap.put(fullclassname, proxy);
            }
            bindview bindannotation = variableelement.getannotation(bindview.class);
            int id = bindannotation.value();
            proxy.putelement(id, variableelement);
        }
        //通过遍历mproxymap,创建java文件
        for (string key : mproxymap.keyset()) {
            classcreatorproxy proxyinfo = mproxymap.get(key);
            javafile javafile = javafile.builder(proxyinfo.getpackagename(),proxyinfo.generatejavacode2()).build();
            try {
                //生成java文件
                javafile.writeto(processingenv.getfiler());
            } catch (ioexception e) {
                mmessager.printmessage(diagnostic.kind.note, " --> create "   proxyinfo.getproxyclassfullname()   "error");
            }
        }
        mmessager.printmessage(diagnostic.kind.note, "process finish ...");
        return true;
    }
} 

此处为了代码演示简单起见,并没有加入格式校验(如对注解修饰的类型等信息进行校验),如果你实际运用apt技术,请务必要对注解的使用规则进行详细的校验。

注解处理器注册

自定义的注解处理器必须经过注册才能够使用,即需要对注解处理器添加自动主动的注解。

我们可以使用google autoservice来进行注解处理器的自动注册,首先需要在注解处理器所在的module的build.gradle文件添加autoservice的包引入:

    //google autoservice
    implementation "com.google.auto.service:auto-service:1.0-rc4"
    annotationprocessor "com.google.auto.service:auto-service:1.0-rc4"

然后将自动注册的注解添加到注解处理器上以实现自动注册效果:

@autoservice(processor.class)
public class bindviewprocessor extends abstractprocessor {
    ...
}

java代码生成

对于java代码的生成存在有多种方式,如字符串拼接,javapoet等。
如果要使用javapoet,则需要在当前的modulebuild.gradle文件中引入:

    //javapoet
    implementation "com.squareup:javapoet:1.13.0"

详细代码如下:

package com.example.shapetest.bindview;
import com.squareup.javapoet.classname;
import com.squareup.javapoet.methodspec;
import com.squareup.javapoet.typespec;
import java.util.hashmap;
import java.util.map;
import javax.lang.model.element.modifier;
import javax.lang.model.element.packageelement;
import javax.lang.model.element.typeelement;
import javax.lang.model.element.variableelement;
import javax.lang.model.util.elements;
public class classcreatorproxy {
    private string mbindingclassname;
    private string mpackagename;
    private typeelement mtypeelement;
    private map mvariableelementmap = new hashmap<>();
    public classcreatorproxy(elements elementutils, typeelement classelement) {
        this.mtypeelement = classelement;
        packageelement packageelement = elementutils.getpackageof(mtypeelement);
        string packagename = packageelement.getqualifiedname().tostring();
        string classname = mtypeelement.getsimplename().tostring();
        this.mpackagename = packagename;
        this.mbindingclassname = classname   "_viewbinding";
    }
    public void putelement(int id, variableelement element) {
        mvariableelementmap.put(id, element);
    }
    /**
     * 创建java代码  字符串拼接方式
     * @return
     */
    public string generatejavacode() {
        stringbuilder builder = new stringbuilder();
        builder.append("package ").append(mpackagename).append(";\n\n");
        builder.append("import com.example..*;\n");
        builder.append('\n');
        builder.append("public class ").append(mbindingclassname);
        builder.append(" {\n");
        generatemethods(builder);
        builder.append('\n');
        builder.append("}\n");
        return builder.tostring();
    }
    /**
     * 加入method    字符串拼接方式
     * @param builder
     */
    private void generatemethods(stringbuilder builder) {
        builder.append("public void bind("   mtypeelement.getqualifiedname()   " host ) {\n");
        for (int id : mvariableelementmap.keyset()) {
            variableelement element = mvariableelementmap.get(id);
            string name = element.getsimplename().tostring();
            string type = element.astype().tostring();
            builder.append("host."   name).append(" = ");
            builder.append("("   type   ")(((android.app.activity)host).findviewbyid( "   id   "));\n");
        }
        builder.append("  }\n");
    }
    public string getproxyclassfullname()
    {
        return mpackagename   "."   mbindingclassname;
    }
    public typeelement gettypeelement()
    {
        return mtypeelement;
    }
    /**
     * 创建java代码   javapoet
     * @return
     */
    public typespec generatejavacode2() {
        typespec bindingclass = typespec
                //class名称设置
                .classbuilder(mbindingclassname)
                //类为public
                .addmodifiers(modifier.public)
                .addmethod(generatemethods2())
                .build();
        return bindingclass;
    }
    /**
     * 加入method   javapoet
     */
    private methodspec generatemethods2() {
        classname host = classname.bestguess(mtypeelement.getqualifiedname().tostring());
        methodspec.builder methodbuilder = methodspec
                //方法名称
                .methodbuilder("bind")
                //方法为public
                .addmodifiers(modifier.public)
                //返回值
                .returns(void.class)
                //方法参数
                .addparameter(host, "host");
        for (int id : mvariableelementmap.keyset()) {
            variableelement element = mvariableelementmap.get(id);
            string name = element.getsimplename().tostring();
            string type = element.astype().tostring();
            methodbuilder.addcode("host."   name   " = "   "("   type   ")(((android.app.activity)host).findviewbyid( "   id   "));\n");
        }
        return methodbuilder.build();
    }
    public string getpackagename() {
        return mpackagename;
    }
}

3. 对外调用

在完成了上述两步之后,apt的实际工作已经能够正常运行了,下面我们实现以下调用方法,以达到模仿butterknife的调用效果。

首先我们需要新建立一个android lib的module,命名为apt_lib

当前的对外lib需要引用注解处理器lib——processortest,在当前module的build.gradle文件中进行如下配置:

    implementation project(path: ':processortest')

新建对外的调用方法,代码如下:、

public class bindviewtools {
    public static void bind(activity activity) {
        class clazz = activity.getclass();
        try {
            class bindviewclass = class.forname(clazz.getname()   "_viewbinding");
            method method = bindviewclass.getmethod("bind", activity.getclass());
            method.invoke(bindviewclass.newinstance(), activity);
        } catch (classnotfoundexception e) {
            e.printstacktrace();
        } catch (illegalaccessexception e) {
            e.printstacktrace();
        } catch (instantiationexception e) {
            e.printstacktrace();
        } catch (nosuchmethodexception e) {
            e.printstacktrace();
        } catch (invocationtargetexception e) {
            e.printstacktrace();
        }
    }
}

其主要功能是为被注解绑定的activity生成对应的辅助类以实现apt被调用的效果。

4. 调用

在进行了如下几步之后,我们实际上已经能够正常的调用@bindview这个注解了.
我们在我们的主module——app中调用apt,当然此时的apt需要引入之前的lib工程,可以在appbuild.gradle文件中进行如下配置:

    implementation project(':annotationtest') // 添加依赖模块
    implementation project(':apt_lib') // 添加依赖模块
    implementation project(':processortest') // 添加依赖模块
    annotationprocessor project(':processortest')

需要注意的是annotationprocessor这一注解处理工具是java语言使用的,如果你使用的是kotlin语言,则需要使用kapt.

此时直接调用我们在开头的调用代码,在编译执行后能够正常运行。

当我们查看工程内的build文件夹时能够在如下路径发现我们之前生成的文件:

看到里面的代码如下:

package com.example.workdemo.activity;
public class annotationtestactivity_viewbinding {
  public void bind(annotationtestactivity host) {
    host.tvannotationtest1 = (android.widget.textview)(((android.app.activity)host).findviewbyid( 2131231102));
    host.tvannotationtest2 = (android.widget.textview)(((android.app.activity)host).findviewbyid( 2131231103));
  }
}

这样的话我们就完成了apt的一个简单示例。

总结

本文主要对android中的apt技术进行了简单说明。apt技术能够利用编译的技术直接生成java逻辑代码,在代码重复度较高的场景中比较适用。

免费资源网,https://freexyz.cn/
返回顶部
顶部
网站地图