Android: 手写butterKnifer

  
手写butterKnifer使用了 apt 技术。APT 是一种处理注解工具,他对项目源代码进行扫描,获取注解。然后通过注解处理器对注解进行操作,生成新的class文件。
apt技术使用三个技术点:
annotation模块(Java Library) 该模块存放的是我们自定义的注解,是一个Java Library
compiler模块 (Java Library) 依赖annotation模块,处理注解并自动生成代码等,同样也是Java Library。
butterKnife模块 (Android Library)  编写关于butterknife的相关代码。

第一部分:我们需要创建一个 annotation模块(Java Library),命名为:bk-annotations,该模块存放的是我们自定义的注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BkBindView {
    int value();
}

第二部分:我们需要创建一个 android library,命名为:bk-butterknife

public class Utils {
    public static <T extends View> T findViewById(Activity activity, int viewId){
        return (T)activity.findViewById(viewId);
    }
}
public interface Unbinder {
    @UiThread
    void unbind();

    Unbinder EMPTY = new Unbinder() {
        @Override
        public void unbind() {
        }
    };
}
public class ButterKnife {

    public static Unbinder bind(Activity activity){
        try {
            Class<? extends Unbinder> clazz = (Class<? extends Unbinder>) Class.forName(activity.getClass().getName() + "_ViewBinding");
            //构造函数
            Constructor<? extends Unbinder> constuctor = clazz.getDeclaredConstructor(activity.getClass());
            constuctor.setAccessible(true);
            Unbinder unbinder = constuctor.newInstance(activity);
            return unbinder;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Unbinder.EMPTY;
    }
}

 

第三部分:我们需要创建一个注解处理器,获取项目注解,通过javapoet生成代码,命名为:bk-compiler

implementation 'com.squareup:javapoet:1.7.0' //这个库的主要作用就是帮助我们通过类调用的形式来生成代码
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation project(path: ':bk-annotations')
/**
 * Created by malei on 6/21/21
 * Describe:定义一个注解处理器
 */
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    private Filer mFiler;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        mFiler = processingEnv.getFiler();
        mElementUtils = processingEnv.getElementUtils();
    }

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

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BkBindView.class.getCanonicalName());
        return types;
    }


    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //1 . 将项目中所有的注解添加到map中,map根据类做key
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BkBindView.class);
        Map<Element,List<Element>> totalMap = new LinkedHashMap<>();  //类名 -》类中注解
        for (Element element : elements) {
            Element enclosingElement = element.getEnclosingElement(); //该注解属于的类
            List<Element> annotationListForClass = totalMap.get(enclosingElement); //在map中通过类名获取注解表
            if (annotationListForClass == null){ //注解表为null的时候
                annotationListForClass = new ArrayList<>(); //创建注解表
                totalMap.put(enclosingElement,annotationListForClass);  //将该注解表 添加到map中
            }
            //如果注解表不为空,就直接将该注解添加到注解表中
            annotationListForClass.add(element);
        }

        // 2. 对拿到的所有注解进行处理
        for (Map.Entry<Element,List<Element>> entry: totalMap.entrySet()){
            //每一个entry 都是一个类中的注解表

            Element enclosingElement = entry.getKey(); //类名 key
            List<Element> bindListForClass = entry.getValue(); //类中的注解表

            /**
             * 拼装这一行代码:
             * public final class MainActivity_ViewBinding implements Unbinder {
             *   private MainActivity target;
             * }
             */
            String activityName = enclosingElement.getSimpleName().toString(); //类名的字符串
            ClassName activityClassName = ClassName.bestGuess(activityName); //创建新的实例
            ClassName unbinderClassName = ClassName.get("com.qiyi.bk_butterknife","Unbinder");

            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding2")
                    .addModifiers(Modifier.FINAL,Modifier.PUBLIC) //类名前添加public final
                    .addSuperinterface(unbinderClassName) //添加类的实现接口
                    .addField(activityClassName,"target",Modifier.PRIVATE);  //添加一个成员变量,这个名字target是仿照butterknife

            /**
             * 拼装这一行代码:
             *  @Override
             *   @CallSuper
             *   public final void unbind() {
             *     target.btn = null;
             *   }
             */
            //实现Unbinder的方法
            //CallSuper这个注解不像Override可以直接拿到,需要用这种方式
            ClassName callSuperClass = ClassName.get("androidx.annotation","CallSuper");
            MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")//和你创建的Unbinder中的方法名保持一致
                    .addAnnotation(Override.class)
                    .addAnnotation(callSuperClass)
                    .addModifiers(Modifier.FINAL, Modifier.PUBLIC);


            /**
             *  MainActivity_ViewBinding(MainActivity target) {
             *     this.target = target;
             *     target.btn = Utils.findViewById(target,2131230807);
             *   }
             */
            //添加构造函数
            MethodSpec.Builder constructMethodBuilder = MethodSpec.constructorBuilder()
                    .addParameter(activityClassName,"target");
            constructMethodBuilder.addStatement("this.target = target");

            for (Element bindViewElement : bindListForClass) {
                //获取一个注解的地方
                String fieldName = bindViewElement.getSimpleName().toString();

                ClassName utilsClassName = ClassName.get("com.qiyi.bk_butterknife", "Utils");
                BkBindView annotation = bindViewElement.getAnnotation(BkBindView.class);
                if (annotation != null){
                    int resId = annotation.value();
                    constructMethodBuilder.addStatement("target.$L = $T.findViewById(target,$L)",fieldName,utilsClassName,resId);

                    //在unbind方法中添加代码 target.textView1 = null;
                    //不能用addCode,因为它不会在每一行代码后加分号和换行
                    unbindMethodBuilder.addStatement("target.$L = null",fieldName);
                }
            }

            classBuilder.addMethod(constructMethodBuilder.build());
            classBuilder.addMethod(unbindMethodBuilder.build());

            try {
                //得到包名
                String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
                log(packageName);
                JavaFile.builder(packageName,classBuilder.build())
                        .addFileComment("butterknife 自动生成")  //添加类的注释
                        .build().writeTo(mFiler);
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
        return false;
    }

    private void log(String log){
        System.out.println("MALEI: " + log);
    }
}

最后在项目中使用

@BkBindView(R.id.btn)
Button btn;

在看产生的代码:

结构目录:

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值