Java注解Annotation
归纳总结一下:
作用
a. 标记,用于告诉编译器一些信息
@Override就是,如果没有正确重载方法,编译不过
b. 编译时动态处理,如动态生成代码
ButterKnife就是此类
c. 运行时动态处理,如得到注解信息
用的比较少,运行时可通过Method.getAnnotation方法得到运行时注解信息
Annotation分类
1 标准Annotation,Override, Deprecated, SuppressWarnings
标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning
2 元Annotation,@Retention, @Target, @Inherited, @Documented
元 Annotation 是指用来定义Annotation的Annotation,在后面 Annotation 自定义部分会详细介绍含义
- @Documented 是否会保存到 Javadoc 文档中
- @Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为CLASS,SOURCE大都为Mark Annotation,这类Annotation大都用来校验,比如Override, SuppressWarnings
- @Target 可以用来修饰哪些程序元素,如TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有
- @Inherited 是否可以被继承,默认为false
3 自定义Annotation
自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
这里是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation,后面在自定义 Annotation 时会具体介绍
@Documented
@Retention(RetentionPolicy.RUNTIME) //运行时注解信息
@Target(ElementType.METHOD) //只能修饰方法
@Inherited
public @interface MethodInfo {
String author() default "trinea@gmail.com";
String date();
int version() default 1;
}
public class App {
@MethodInfo(author = "trinea.cn+android@gmail.com", date = "2014/02/14", version = 2)
public String getAppName() {
return "trinea";
}
}
Annotation解析
@Retention为SOURCE,就没啥解析了,可以看到编译后的class文件都木有@Override注解
//Annotations are to be discarded by the compiler.
只是为了校验,class字节码文件中不存在该注解信息@Retention为CLASS
//Annotations are to be recorded in the class file by the compiler . but need not be retained by the VM at run time. This is the default behavior.注解信息被记录到class文件中,但是在运行时被忽略。
编译器在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的process方法去处理
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env)SupportedAnnotationTypes
表示这个 Processor 要处理的 Annotation 名字。process 函数中参数annotations
表示待处理的 Annotations,参数env
表示当前或是之前的运行环境process 函数返回值表示这组annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理@Retention为RUNTIME
//Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
如果可以看到运行时可以拿到注解消息
public class Person {
public static void main(String[] args) {
try {
Class cls = Class.forName("App");
for (Method method : cls.getMethods()) {
MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); //必须是RUNTIME类型
if (methodInfo != null) {
System.out.println("method name:" + method.getName());
System.out.println("method author:" + methodInfo.author());
System.out.println("method version:" + methodInfo.version());
System.out.println("method date:" + methodInfo.date());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
ButterKnife源码解析
简单用法
public class MainActivity extends AppCompatActivity {
@Bind(R.id.imageview)
ImageView imageview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
imageview.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
}
@OnClick(R.id.imageview)
void onClickImage() {
Toast.makeText(this, "on click", Toast.LENGTH_SHORT).show();
}
}
实现原理
注解申明
Bind注解的申明,此时可以看到编译期间注解
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
int[] value();
}
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
int[] value() default { View.NO_ID };
}
ButterKnifeProcessor.process编译期间生成辅助class文件
由上面《Annotation解析》章节可以得知,@Retention为CLASS的解析必须继承自 AbstractProcessor 的类,然后调用他们的process方法去处理
public final class ButterKnifeProcessor extends AbstractProcessor {
..........
@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();
try {
JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
Writer writer = jfo.openWriter();
writer.write(bindingClass.brewJava());
writer.flush();
writer.close();
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
..........
}
所以只要添加了butterknife的注解信息,那么编译期间就会去调用ButterKnifeProcessor类的process方法,这个方法主要目的就是编译期间生成额外的几个class字节码文件,具体是怎么做到的,就细谈了,也看不懂,只需要知道结果
MainActivity.class
public class MainActivity
extends AppCompatActivity
{
@Bind({2131492969}) //编译期间,注解所以存在于class文件
ImageView imageview;
protected void onCreate(Bundle savedInstanceState)
{ //@Override是在SOURCE标注,所以class文件中没有
super.onCreate(savedInstanceState);
setContentView(2130968601);
ButterKnife.bind(this);
this.imageview.setOnTouchListener(new View.OnTouchListener()
{
public boolean onTouch(View v, MotionEvent event)
{
return false;
}
});
}
@OnClick({2131492969})
void onClickImage()
{
Toast.makeText(this, "on click", 0).show();
}
}
MainActivity$1.class
这个就不用说啦,匿名内部类,implements View.OnTouchListener
MainActivity$$ViewBinder.class
ButterKnifeProcessor类的process方法生成的,注意此时实现了ViewBinder接口, 关键bind方法
target,source都是上面我们的MainActivity对象,可以看到此时对imageview属性通过Finder对象进行了赋值,同时对imageview绑定了点击事件,是就是回调MainActivity的onClickImage方法
public class MainActivity$$ViewBinder<T extends demo.lbb.test.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492969, "field 'imageview' and method 'onClickImage'");
target.imageview = finder.castView(view, 2131492969, "field 'imageview'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(
android.view.View p0
) {
target.onClickImage();
}
});
}
@Override public void unbind(T target) {
target.imageview = null;
}
}
MainActivity$$ViewBinder$1.class
这个当然也是上面的匿名内部类了,extends DebouncingOnClickListener
运行时
onCreate方法中bind方法必定是要调用的,否则就算编译就算生成了ViewBinder的字节码文件,但是运行时类加载器是不会去加载ViewBinder的,所以无效
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
ButterKnife的bind方法
public static void bind(Activity target) {
bind(target, target, Finder.ACTIVITY);//finder类型为Activity
}
static void bind(Object target, Object source, Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass); //此时得到的viewBinder就是上面的MainActivity$$ViewBinder.class了
if (viewBinder != null) {
viewBinder.bind(finder, target, source);//哈,调用MainActivity$$ViewBinder.class的bind方法, 此时target,source都是MainActivity对象了
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) //cls就是MainActivity.class
throws IllegalAccessException, InstantiationException {
ViewBinder<Object> viewBinder = BINDERS.get(cls); //BINDERS集合中查找是否之前加载过
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
String clsName = cls.getName(); //clsName:demo.lbb.test.MainActivity
if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
try {
Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX); //哈,此时加载clsName:demo.lbb.test.MainActivity$$ViewBinder.class
//noinspection unchecked
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
viewBinder = findViewBinderForClass(cls.getSuperclass());
}
BINDERS.put(cls, viewBinder); //保存在BINDERS集合中
return viewBinder;
}
最后来看看MainActivity$$ViewBinder中的调用Finder类的findRequiredView方法查找view的过程,最后是执行了((Activity) source).findViewById(id)
public enum Finder {
........
ACTIVITY {
@Override protected View findView(Object source, int id) {
return ((Activity) source).findViewById(id);
}
@Override public Context getContext(Object source) {
return (Activity) source;
}
},
public <T> T findRequiredView(Object source, int id, String who) {
T view = findOptionalView(source, id, who);
if (view == null) {
String name = getContext(source).getResources().getResourceEntryName(id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' annotation.");
}
return view;
}
public <T> T findOptionalView(Object source, int id, String who) {
View view = findView(source, id);
return castView(view, id, who);
}
protected abstract View findView(Object source, int id);
.......
总结
只是说了下大概的流程,最关键的其实还是ButterKnifeProcessor.process方法,通过这个方法编译期间生成了辅助的ViewBinder字节码文件,然后通过ButterKnife.bind(this);
的执行,就会去调用findViewById和setOnClickListener等方法
ButterKnifeProcessor.process怎么生成的ViewBinder字节码文件,现阶段我还不需要去了解,以后有时间去研究吧
优点:
代码简洁,少了很多重复性代码
缺点:
为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间。
从上可以看到onCreate中的ButterKnife.bind(this);
同时是在main线程中执行了很多流程,需要一定的时间,界面显示onResume之后,所以多少会有点延迟,同时更多的占用了内存