注解使用--ButterKnife原理分析

#目录

1. 什么是注解
2. 分类
3. 定义注解
4. 定义注解处理器
5. ButterKnife原理

#1.什么是注解

JDK 5增加了注解,是代码中的特殊标志,可在编译,类加载是,运行时被读取并进行相应操作。

#2.分类

2.1 标准注解

@override : 对父类方法进行重写标志
@Deprecated : 过时方法添加的标志
@SuppressWarning : 选择性取消特定代码的警告
@SafeVarargs : 声明可变长度参数方法

2.2 元注解

元注解就是用于注解其他注解
@Target : 注解所修饰的对象范围
@Inherited:表示注解可被继承
@Documented: 表示注解应为JavaDoc工具记录
@Retention: 声明注解保留策略
@Repeatable:允许一个注解在同一个声明类型上多次使用

注:

#@Target 取值为ElementType 类型数组

TYPE: 修饰类,接口,枚举类型
FIELD: 修饰成员变量
METHOD: 修饰方法
PARAMETER: 修饰参数
CONSTRUCTOR: 修饰构造函数
LOCAL_VARIABLE: 修饰局部变量
ANNOTATION_TYPE: 修饰注解
PACKAGE: 修饰包
TYPE_USE: 使用类型

#@Retention 取值有三种不同级别保留策略

RetentionPolicy.SOURCE: 源码级别,注解信息只保留在.java源码中,编译后被丢弃
RetentionPolicy。CLASS: 编译级别,注解信息会保留在.java源码和.class中,运行时JVM会丢弃该注解信息。
RetentionPolicy.RUNTIME: 运行时注解,运行时JVM也会保留该注解信息

#3.定义注解

3.1.定义编译时注解

1.注解

	@Rentention(RetentionPolicy.CLASS)
	public @interface Man {
		String name() default "霍华德";
		int age() default 23;
	}

2.编译时注解处理器

public class ClaaProcessor extends AbstractProcessor{
	@override
	public synchronized void init(ProcessingEnvironment var1) {
        //注解工具初始化,ProcessingEnvironment提供很多工具类,如
        //Elements、Types、Filer、Messager、SourceVersion
    }
    @override
    public boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2){
    	//每个处理器的主函数,编写扫描处理注解的代码,以及生成Java文件。入参RoundEnviroument,可查询包含指定注解的被注解元素。
		...
		return true;
	}
	
	@override
	public SourceVersion getSupportedSourceVersion() {
	//指定使用的Java版本
		return SourceVersion.latestSupported();
	}

	@override
	public Set<String> getSupportedAnnotationTypes() {
	//指定该注解处理器是注册给哪个注解的,返回值是字符串集合,包含注解类型的全称。
		Set<String> annotations = new LinkedHashSet<String>();
		annotations.add(BindView.class.getCannonicalName());
		return annotations;
	}
}

3.注册注解处理器

可以使用Google开源的AutoService进行注册,依赖中导入

	dependencies {
		implementation 'com.google.auto.service:auto-service:1.0-rc2'
	}

使用:

@AutoService(Processor.class)
public class ClaaProcessor extends AbstractProcessor{
	...
} 

3.2.定义运行时注解

1.注解

	@Rentention(RetentionPolicy.RUNTIME)
	@Target(MENTHOD)
	@Documented
	public @interface GET {
		String value() default "";
	}

2.使用注解

public class Test (){
	@GET(value = "http://baidu.com")
	public String getMethod(){
		return "";
	}	
}

3.运行时注解处理器
运行时注解处理器需要用到反射机制

public class AnnnotationProcessor {
	public static void main (String[] args){
		Menthods methods = Test.class.getDeclaredMethods();
		for (Method method : methods){
			GET get = method.getAnnotation(GET.class);
			System.out.plintln (get.value());
		}
	}
}

#4.ButterKnife原理解析

4.1.使用

#导入依赖

//project..build.gradle添加
	dependencies {
        classpath "com.jakewharton:butterknife-gradle-plugin:10.0.0"
	}
//app.build.gradle添加
apply plugin: "com.neenbedankt.android-apt"

dependencies {
    implementation 'com.jakewharton:butterknife:10.0.0'
    apt 'com.jakewharton:butterknife-compiler:10.0.0'
}
public class MainActivity extends AppCompatActivity {
	//绑定控件
	@BindView(R.id.text_tv)
	TextView mText;
	...
	@Override
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(saveInstanceState);
		setContentView(R.layout.activity_main);
		ButterKnife.bind(this);
	} 
	//绑定监听
	@OnClick(R.id.text_tv)
	public void onClick(View v){
		
	}

	@OnTextChanged(R.id.text_tv)
	void onTextChanged(charSequence s, int start, int before , int count){
		
	}
}

4.2.原理

ButterKnife使用的是编译时注解

	@Rentention(RetentionPolicy.CLASS)
	@Target(FIELD)
	public @interface BindView{
		@IdRes int value() ;
	}

4.2.1.ButterKnifeProcessor源码分析

public final class ButterKnifeProcessor extends AbstractProcessor{
	...
	@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    	Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);// 1
		for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {//2
      		TypeElement typeElement = entry.getKey();
      		BindingSet binding = entry.getValue();
      		JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        	javaFile.writeTo(filer); //3
      } catch (IOException e) {
        	error(typeElement,"Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }

}

注释1出findAndParseTargets

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
	Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
	...
	for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);//1
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    ...
    return bindingMap;
}

注释1 parseBindView

  private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames){
	    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
		boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element); //1
        ...
        if (hasError) {
    		  return;
	    }
	    int id = element.getAnnotation(BindView.class).value();//2
		Id resourceId = elementToId(element, BindView.class, id);
    	if (builder != null) {
      		String existingBindingName = builder.findExistingBindingName(resourceId);
       if (existingBindingName != null) {
         error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(resourceId, new FieldViewBinding(name, type, required));//3

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);

}

注释1 处 isInaccessibleViaGeneratedCode检查

  1. 修饰符不能为private和static
  2. 包含类型不能为非Class

isBindingInWrongPackage检查包名不能以android.和java.开头

注释2 获取注解的标注值

注释3 将注解修饰 的类型信息及注解的成员变量存储到BindingSet中


回到process方法中的注释2,通过遍历Map.Entry<TypeElement, BindingSet>集合,获取BindingSet对象,并调用BindingSet#brewJava方法,生成JavaFile文件,注释3处将其输出为Java文件。文件名使用注解类名 +_ViewBinding 如MainActivity_ViewBinding


4.2.2.ButterKnife的bind方法

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
  //获取Activity的DecorView
    View sourceView = source.getWindow().getDecorView();
    return bind(target, sourceView);
  }
 @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);//1

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);//2
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

注释1处的 findBindingConstructorForClass方法

@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);//1
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
...
    try {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//2
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);//3
    } catch (ClassNotFoundException e) {
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor);//4
    return bindingCtor;
  }

注释1从BINDING中获取对应Class的Constructor实例,它的定义如下
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
以Class为Key,Constructor作为value的LinkedHashMap。

注释2通过反射生成Class类,并缓存到BINDINGS中。最后返回该类的Constructor

最后在bind函数的注释2处通过该Constructor实例化MainActivity_ViewBinding对象


4.2.3.生成辅助类分析

上面例子中我们在MainActivity中使用ButterKnife,绑定组件。因此生成的辅助类名为 MainActivity_ViewBinding

public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;
   @UiThread
  public MainActivity_ViewBinding(T target, View source) {
    this.target = target;
    target.mText = Utils.findRequiredViewAsType(source, R.id.text_tv, "field 'mText'", TextView.class);//1

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

**注释1处findRequiredViewAsType **

  public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
    View view = findRequiredView(source, id, who);//2
    return castView(view, id, who, cls);//将View强转为传入的Class类型,本例中就是TextView.class.
  }

注释2 处findRequiredView

public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);//通过findViewById将id对应的View返回
    if (view != null) {
      return view;
    }
    ...
}
注解使用--ButterKnife原理分析注解使用--ButterKnife原理分析 Brin233 发布了9 篇原创文章 · 获赞 3 · 访问量 2492 私信 关注
上一篇:Android使用AnnotationProcessor模仿ButterKnife


下一篇:Android利用注解自定义一个超级简单的ButterKnife