博客原地址:从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输入文件,也不能对方法做修改或者添加。
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()方法方法中被包装处理点击事件。
5 参考链接
How ButterKnife actually works?
butterknife 源码分析
最新ButterKnife框架原理
Java注解