【技术译文】安卓从java注解分析ButterKnife工作流程

博客原地址:从java注解分析ButterKnife工作流程
译文原链接:How ButterKnife actually works?
翻译修改:Anthony

在我的上一篇文章中,绝对不容错过,ButterKnife使用详谈中,讲解了对ButterKnife的使用。这篇文章将接着一篇文章使用之后,对ButterKnife的工作流程进行概要分析。这里Butterknife分析来自参考自链接How ButterKnife actually works?,并作出部分修改。这里做一个整理和学习。
考虑到ButterKnife的入口使用java注解,比如。

class ExampleActivity extends Activity {
 @Bind(R.id.user) EditText username;
 @Bind(R.id.pass) EditText password;
@OnClick(R.id.submit) void submit() {
 // TODO call server…
 }
@Override public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.simple_activity);
 ButterKnife.bind(this);
 // TODO Use fields…
 }
}

首先这里会对java注解(java annotation)分析,然后面接上Butterknife的工作流程。

1 什么是java注解?

在java语法中,使用@符号作为开头,并在@后面紧跟注解名。被运用于类,接口,方法和字段之上,例如:

@Override
void myMethod() { 
......
}

这其中@Override就是注解。这个注解的作用也就是告诉编译器,myMethod()方法覆写了父类中的myMethod()方法。

2 java中内置的注解

java中有三个内置的注解:

@Override,表示当前的方法定义将覆盖超类中的方法,如果出现错误,编译器就会报错。
@Deprecated:如果使用此注解,编译器会出现警告信息。
@SuppressWarnings:忽略编译器的警告信息。

2.1 @Override 注解

当我们的子类覆写父类中的方法的时候,我们使用这个注解,这一定程度的提高了程序的可读性也避免了维护中的一些问题,比如说,当修改父类方法签名(方法名和参数)的时候,你有很多个子类方法签名也必须修改,否则编译器就会报错,当你的类越来越多的时候,那么这个注解确实会帮上你的忙。如果你没有使用这个注解,那么你就很难追踪到这个问题。
示例:

public class MyParentClass {

    public void justaMethod() {
        System.out.println("Parent class method");
    }
}

public class MyChildClass extends MyParentClass {

    @Override
    public void justaMethod() {
        System.out.println("Child class method");
    }
}
2.2 @Deprecated注解

一个弃用的元素(类,方法和字段)在java中表示不再重要,它表示了该元素将会被取代或者在将来被删除。
当我们弃用(deprecate)某些元素的时候我们使用这个注解。所以当程序使用该弃用的元素的时候编译器会弹出警告。当然我们也需要在注释中使用@deprecated标签来标示该注解元素。
示例:

class DeprecatedDemo {
   /* @deprecated This field is replaced by 
    * MAX_NUM field
    */
   @Deprecated
   int num=10;
    
   final int MAX_NUM=10;
    
   /* @deprecated As of release 1.5, replaced 
    * by myMsg2(String msg, String msg2)
    */
   @Deprecated
   public void myMsg(){
       System.out.println("This method is marked as deprecated");
   }
     
   public void myMsg2(String msg, String msg2){
       System.out.println(msg+msg2);
   }
    
   public static void main(String a[]){      
        DeprecatedDemo obj = new DeprecatedDemo();
        obj.myMsg();
        System.out.println(obj.num);
   }
}
2.3 @SuppressWarnings注解

当我们想让编译器忽略一些警告信息的时候,我们使用这个注解。比如在下面这个示例中,我们的deprecatedMethod()方法被标记了@Deprecated注解,所以编译器会报警告信息,但是我们使用了@SuppressWarnings("deprecation")也就让编译器不在报这个警告信息了。

@SuppressWarnings("deprecation")
    void myMethod() {
        myObject.deprecatedMethod();
}

3 自定义注解

3.1 来看看一个自定义注解

可以通过下面这种形式添加自己的自定义注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomAnnotation{
    int studentAge() default 18;
    String studentName();
    String stuAddress();
    String stuStream() default "CSE";
}

自定义注解使用@interface来声明一个注解,这里对这个自定义的注解进行使用:

@MyCustomAnnotation(
    studentName="Chaitanya",
    stuAddress="Agra, India"
)
public class MyClass {
...
}

可以看到上面的studentAge 和stuStream字段已经在注解定义阶段设置了默认值(当然也可以对这些默认值进行修改),studentName和stuAddress没有默认值,在使用的时候必须定义值。

3.2 元注解

在上面的注解定义阶段,你一定注意到了这四个注解,那么他们是什么呢?

(1).@Target,    
(2).@Retention,   
(3).@Documented,   
(4).@Inherited

这是java 5.0之中引入的四个元注解,元注解也就是负责注解其他的注解。
那么来分别看看这四个注解是什么意思?
(1)@Target

表示该注解用于什么地方,可能的ElementType参数包括:
CONSTRUCTOR:构造器的声明
FIELD:域声明
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类,接口或enum声明

比如说这个注解表示只能在方法中使用。

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
public @interface MyCustomAnnotation {

}
public class MyClass {
   @MyCustomAnnotation
   public void myMethod()
   {
    ......
   }
}

(2)@Retention

表示在什么级别保留此信息,可选的RetentionPolicy参数包括:
SOURCE:注解仅存在代码中,注解会被编译器丢弃
CLASS:注解会在class文件中保留,但会被VM丢弃
RUNTIME:VM运行期间也会保留该注解,因此可以通过反射来获得该注解

比如说这个注解表示VM运行期间也会保留该注解。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface MyCustomAnnotation {
}

(3)@Documented

将注解包含在javadoc中

示例:

java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}

(4)@Inherited

允许子类继承父类中的注解

示例,这里的MyParentClass 使用的注解标注了@Inherited,所以子类可以继承这个注解信息:

java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}
@MyCustomAnnotation
public class MyParentClass { 
  ... 
}
public class MyChildClass extends MyParentClass { 
   ... 
}

4 ButterKnife工作流程解析

有了上面的java注解的基础知识,那么接着学习。

4.1 java 注解工作流程

1 注解是在编译(compile)时期进行处理的。
2 注解处理器(Annotation Processor)读取java代码处理相应的注解,并且生成对应的代码。
3 生成的java代码被当做普通的java 类再次编译。
4 注解处理器不能修改存在java输入文件,也不能对方法做修改或者添加。

【技术译文】安卓从java注解分析ButterKnife工作流程_第1张图片
java编译流程
4.2 ButterKnife对应的注解工作流程

当你编译你的Android工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作:

1 开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等。
2 当它发现一个类中含有任何一个注解时, ButterKnifeProcessor会帮你生成一个Java类,名字<类名>$$ViewInjector.java,这个新生成的类实现了ViewBinder接口。
3 这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。
4 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。

4.3 实例分析

首先ExampleActivity 中使用ButterKnife,如下面所示

class ExampleActivity extends Activity {
 @Bind(R.id.user) EditText username;
 @Bind(R.id.pass) EditText password;
@OnClick(R.id.submit) void submit() {
 // TODO call server…
 }
@Override public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.simple_activity);
 ButterKnife.bind(this);
 // TODO Use fields…
 }
}

接着编译器就会生成ExampleActivity$$ViewBinder.java文件

public class ExampleActivity$$ViewBinder implements ViewBinder {
 @Override public void bind(final Finder finder, final T target, Object source) {
  View view;
  view = finder.findRequiredView(source, 21313618, “field ‘user’”);
  target.username = finder.castView(view, 21313618, “field ‘user’”);
  view = finder.findRequiredView(source, 21313618, “field ‘pass’”);
  target.password = finder.castView(view, 21313618, “field ‘pass’”);
  view = finder.findRequiredView(source, 21313618, “field ‘submit’ and method ‘submit’”);
  view.setOnClickListener(
    new butterknife.internal.DebouncingOnClickListener() {
       @Override public void doClick(android.view.View p0) {
          target.submit();
       }
    });
  }
 @Override public void reset(T target) {
   target.username = null;
   target.password = null;
 }
}

接着在程序执行的时候,流程就是这样的

1ButterKnife调用findViewBinderForClass
(ExampleActivity.class)
方法查找ExampleActivityViewBinder.bind()方法被执行,查找view以及处理view的类型转换,并设置给 ExampleActivity.class的相应属性(这也就是为什么相应的属性在ButterKnife中必须为Public的,因为在这里会进行访问。当然如果你不考虑性能,也可以采用反射的方式访问private的属性,显然作者没有做这么做)
3 onClickListeners也在 ExampleActivity$$ViewBinder.bind()方法方法中被包装处理点击事件。

【技术译文】安卓从java注解分析ButterKnife工作流程_第2张图片

5 参考链接

How ButterKnife actually works?
butterknife 源码分析
最新ButterKnife框架原理
Java注解

你可能感兴趣的:(【技术译文】安卓从java注解分析ButterKnife工作流程)