从无到有手写ButterKnife框架

导航

一、代码的演进
二、butterKnife反射调用
三、javapoet自动生成模板代码
四、apt与注解
五、注解支持多层继承
六、apt调试
七、javapoet语法

1、前言

Annotation Processing Tool (apt)是编译期处理注解的工具

注解有三种【SOURCE,RUNTIME, CLASS】源码,运行时,编译,其中编译期效率最高的。

解决问题:通过注解,动态生成模板代码

apt与javapoet联动
1、抽象模板关键参数(只关注参数)
2、定义注解传入关键参数
3、引入apt
  • 1、抽象模板参数
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY CHENTONG.
 */
public class FirstActivity_ViewBinding implements Unbinder {
  private FirstActivity target;

  @UiThread
  public FirstActivity_ViewBinding(FirstActivity target, View source) {
    this.target = target;
    target.helloTv = source.findViewById( R.id.helloTv );
  }

  @Override
  public void unbind() {
    target.helloTv = null;
  }
}

这个类中核心的一句话
target.helloTv = source.findViewById( R.id.helloTv );
因为获得注解的同时,就会获得当前元素(Type,Field,Method),所以helloTv,这个field不用关注
关键参数就是元素ResId。

  • 2 定义注解
eg: @ViewId(R.id.helloTv)

1、新建module 选择选择java library ,工程名poet-annotation
2、新建注解 ViewId

@Retention(CLASS) @Target(FIELD)
public @interface ViewId {
    /** View ID to which the field will be bound. */
    @IdRes int value();
}
  • 3、apt
    新建module 命名为poet-compiler
    其中编写的时候,要注意包
    javax.annotation.processing
    javax.lang.model
    方法用法

base类 方便后续开发

public abstract class BaseProcessor extends AbstractProcessor {

    protected Filer mFiler; //输出位置
    protected Logger logger;
    protected Types types;
    protected Elements elementUtils;

    // Module name, maybe its 'app' or others
    protected String moduleName = null;
    protected Map<String, String> options = null;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init( processingEnv );
        mFiler = processingEnv.getFiler();
        types = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        logger = new Logger( processingEnv.getMessager() );
        options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty( options )) {
            moduleName = options.get( KEY_MODULE_NAME );
        }
        if (StringUtils.isNotEmpty( moduleName )) {
            logger.info( "The user has configuration the module name, it was [" + moduleName + "]" );
        } else {
            logger.error( NO_MODULE_NAME_TIPS );
        }
    }

    //判断当前是不是activity类
    public boolean isActivity(TypeElement typeElement){
        TypeMirror activityTm = elementUtils.getTypeElement(Consts.ACTIVITY).asType();
        if (types.isSubtype(typeElement.asType(),activityTm )) return true;
        return false;
    }

    //判断当前类是fragment
    public boolean isFragment(TypeElement typeElement){
        TypeMirror fragmentTm = elementUtils.getTypeElement(Consts.FRAGMENT).asType();
        TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();
        if (types.isSubtype(typeElement.asType(),fragmentTm )
                || types.isSubtype(typeElement.asType(),fragmentTmV4 )){
            return true;
        }
        return false;
    }

    @Override
    public Set<String> getSupportedOptions() {
        return new HashSet<String>() {{
            this.add( KEY_MODULE_NAME );
        }};
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

注解生成代码类

@AutoService(Processor.class)
public class ViewIdProcessor extends BaseProcessor {

    private Map<TypeElement, List<Element>> parentAndChild = new HashMap<>();  //包含父类的注解

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init( processingEnv );
        logger.info(">>> ViewIdProcessor init. <<<");
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        if (CollectionUtils.isNotEmpty( annotations )){

            Set<? extends Element> viewIdElements = roundEnv.getElementsAnnotatedWith(ViewId.class);
            try {
                categories( viewIdElements );

                //支持多层注解 field字段去重  不需要注释掉即可
                supportSuperAnnotation();

                gennerateHelper();
            } catch (Exception e) {
                logger.error( e );
            }

            return true;
        }
        return false;
    }

    //自动生成代码
    private void gennerateHelper() throws IOException {
        //根据类路径获得类型
        TypeElement type_unbinder = elementUtils.getTypeElement(Consts.UNBINDER);

        //用于判断当前类类型
        TypeMirror viewTm =  elementUtils.getTypeElement( Consts.VIEW ).asType();

        TypeElement uiThreadType =  elementUtils.getTypeElement( Consts.UI_THREAD );

        if (MapUtils.isNotEmpty( parentAndChild )){

            for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()){
                TypeElement typeElement = entry.getKey();
                List<Element> elementList = entry.getValue();

                //类全路径
                String qualifiedName = typeElement.getQualifiedName().toString();
                //包名
                String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf("."));
                //类文件
                String fileName = typeElement.getSimpleName() + SUFFIX;

                //新建类Target_ViewBinding
                TypeSpec.Builder targetClassType = TypeSpec.classBuilder( fileName )
                        .addModifiers( Modifier.PUBLIC)
                        .addJavadoc( WARNING_TIPS )
                        .addSuperinterface( ClassName.get(type_unbinder.asType()) ); //实现接口

                //新建field target字段
                FieldSpec targetField = FieldSpec.builder(TypeName.get( typeElement.asType() ),"target",Modifier.PRIVATE)
                        .build();

                //target类增加一行field
                targetClassType.addField( targetField );


                //新建构造方法Target_ViewBinding(Target target,View source)
                MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                        .addAnnotation( ClassName.get( uiThreadType ) )
                        .addModifiers( Modifier.PUBLIC )
                        .addParameter( TypeName.get( typeElement.asType() ),"target" )
                        .addParameter( TypeName.get( viewTm ),"source" )
                        .addStatement( "this.target = target" );

                for (Element element :elementList){

                    //获取控件ID
                    ViewId viewIdAnnotation = element.getAnnotation( ViewId.class );
                    int viewId = viewIdAnnotation.value();

                    //获取当前field字段
                    String fieldName = element.getSimpleName().toString();
                    constructorBuilder.addStatement( "target."+fieldName + " = source.findViewById( $L )" ,viewId);
                }

                //创建构造方法
                MethodSpec  constructor = constructorBuilder.build();

                //target类增加构造方法
                targetClassType.addMethod( constructor );

                //新建方法 unbind
                MethodSpec.Builder unbindBuilder = MethodSpec.methodBuilder("unbind")
                        .addAnnotation( Override.class )
                        .addModifiers( Modifier.PUBLIC )
                        .returns( void.class );

                for (Element element :elementList){
                    String fieldName = element.getSimpleName().toString();
                    unbindBuilder.addStatement( "target."+fieldName+" = null" );
                }
                //创建释放方法
                MethodSpec unbinder = unbindBuilder.build();

                //target类增加释放方法
                targetClassType.addMethod( unbinder );

                //创建target类
                TypeSpec targetType = targetClassType.build();

                //写类
                JavaFile.builder(packageName, targetType).build().writeTo(mFiler);
                //打印
                JavaFile.builder(packageName, targetType).build().writeTo(System.out);
            }

        }
    }

    /**
     *
     * @param elements
     * @throws IllegalAccessException
     *  当前类type元素和当前类元素的列表
     */
    private void categories(Set<? extends Element> elements) throws IllegalAccessException {
        if (CollectionUtils.isNotEmpty(elements)) {
            for (Element element : elements) {
                //获得当前元素的TypeElement
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

                if (element.getModifiers().contains(Modifier.PRIVATE)) {
                    throw new IllegalAccessException("The inject fields CAN NOT BE 'private'!!! please check field ["
                            + element.getSimpleName() + "] in class [" + enclosingElement.getQualifiedName() + "]");
                }

                if (parentAndChild.containsKey(enclosingElement)) { // Has categries
                    parentAndChild.get(enclosingElement).add(element);
                } else {
                    List<Element> childs = new ArrayList<>();
                    childs.add(element);
                    parentAndChild.put(enclosingElement, childs);
                }
            }

            logger.info("categories finished.");
        }
    }

    /**
     * Field元素 注解支持多层继承
     * 未做算法优化,仅做测试
     */
    private void supportSuperAnnotation(){
        TreeUtils tree = new TreeUtils( );
        parentAndChild = tree.supportSuperAnnotation( parentAndChild );
    }

    //建议这种写法,减少改字符串
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return new HashSet<String>() {{
            this.add( ViewId.class.getName() );
        }};
    }

}
从无到有手写butterKnife框架

https://github.com/yinlingchaoliu/JavaPoetDemo

具体参考
https://cloud.tencent.com/developer/article/1006210

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容