编译期注解框架浅析

编译期注解框架浅析

简介

由于Android开发已经进入一定规模,所以开发效率和代码的简洁开始引发人们的注意,而android对于性能要求比较高,所以基于反射已经无法满足,所以编译期注解也就火了起来。

首先理解下编译期注解的原理,编译期注解就是在代码编译后会生成一些全新的代码,而在执行阶段,就会使用这些生成的代码。所以编译期注解的本质就是“生成代码”,如何生成,说了可能都不相信,是拿字符串拼接出的。所以编译期注解不是解决一些问题的方法而仅仅是为了简化代码的方案。如果你愿意多写一些代码,完全可以不使用它。

编译期注解要素

1.注解
2.处理注解的类(也是这个类生成代码)
3.api接口,这里要注意了编译期注解怎么也是一个静态动作,不是执行时候做的,所以你想让生成的代码工作肯定得调api了

白话流程

这里指的是我们的代码写完之后,框架的执行流程
首先,编译期代码的时候,会生成一份规定格式的代码(后面简称”苦逼类”),然后当我们调用某个api的时候,就会通过反射去生成那个”苦逼类”,然后去执行对应的方法,但执行哪个方法呢,肯定是有个接口类了,无论我们生成哪个”苦逼类”都会掉哪个接口方法。

代码操练

由于我比较懒,就不写了,找到butterknife的几个核心类的核心方法,贴出来,到时候大家修改写也能写个。

首先是注解:

    @Retention(CLASS) @Target(FIELD)
    public @interface BindView {
      /** View ID to which the field will be bound. */
      @IdRes int value();
    }

处理注解的类:

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
    @Override 
    public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    trees = Trees.instance(processingEnv);
  }

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

  @Override 
  public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

      for (JavaFile javaFile : bindingClass.brewJava()) {
        try {
          javaFile.writeTo(filer);
        } catch (IOException e) {
          error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
              e.getMessage());
        }
      }
    }

    return true;
  }

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

ok,主要就是这四个方法,其中生成代码,使用了com.squareup.javapoet.JavaFile这个类帮助完成。

执行api:

public final class ButterKnife {
    public static Unbinder bind(@NonNull Activity target) {
    Class<?> targetClass = target.getClass();
    ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
      if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
      return viewBinder.bind(Finder.DIALOG, target, source);
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER.bind(Finder.DIALOG, target, source);
    }
    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      Class<?> viewBindingClass = Class.forName(clsName + "_ViewBinder");
      //noinspection unchecked
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
    BINDERS.put(cls, viewBinder);
    return viewBinder.bind(Finder.DIALOG, target, source);;
  }

}

其实就是通过类名反射生成一个”苦逼类”对象,然后执行它的bind方法。
说好的接口:

public interface ViewBinder<T> {
  Unbinder bind(Finder finder, T target, Object source);
}

最后让我们来看下我生成的一个“苦逼类”源码

// Generated code from Butter Knife. Do not modify!

import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.Finder;
import butterknife.internal.ViewBinder;
import java.lang.IllegalStateException;
import java.lang.Object;
import java.lang.Override;

public class TestActivity$$ViewBinder<T extends TestActivity> implements ViewBinder<T> {
  @Override
  public Unbinder bind(Finder finder, T target, Object source) {
    return new InnerUnbinder<>(target, finder, source);
  }

  protected static class InnerUnbinder<T extends TestActivity> implements Unbinder {
    protected T target;

    protected InnerUnbinder(T target, Finder finder, Object source) {
      this.target = target;

      target.mtv = finder.findRequiredViewAsType(source, 2131492981, "field 'mtv'", TextView.class);
    }

    @Override
    public void unbind() {
      T target = this.target;
      if (target == null) throw new IllegalStateException("Bindings already cleared.");

      target.mtv = null;

      this.target = null;
    }
  }
}

大家可以看到是实现了上面那个接口吧。

ok,原理其实并不复杂,希望大家能多用编译期注解时框架,写出更多简洁的代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值