咱们平常使用的不少开源库都有注解的实现,主要有运行时注解和编译时注解两种。html
运行时注解:主要做用就是获得注解的信息java
Retrofit:android
调用
segmentfault
@GET("/users/{username}") User getUser(@Path("username") String username);
定义api
@Documented @Target(METHOD) @Retention(RUNTIME) @RestMethod("GET") public @interface GET { String value(); }
编译时注解:主要做用动态生成代码
框架
Butter Knife
eclipse
调用ide
@InjectView(R.id.user) EditText username;
定义ui
@Retention(CLASS) @Target(FIELD) public @interface InjectView { int value(); }
要实现编译时注解须要3步:
this
一、定义注解(关于注解的定义能够参考下面引用的博客)
import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; @Target({ ElementType.FIELD, ElementType.TYPE }) @Retention(RetentionPolicy.CLASS) public @interface Seriable { }
二、编写注解解析器
@SupportedAnnotationTypes("annotation.Seriable") @SupportedSourceVersion(SourceVersion.RELEASE_6) public class ViewInjectProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element ele : roundEnv.getElementsAnnotatedWith(InjectView.class)) { if(ele.getKind() == ElementKind.FIELD){ //todo } return true; }
@SupportedAnnotationTypes,定义要支持注解的完整路径,也能够经过getSupportedAnnotationTypes方法来定义
@Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(InjectView.class.getCanonicalName()); return types; }
@SupportedSourceVersion(SourceVersion.RELEASE_6)表示支持的jdk的版本
三、建立制定文件resources/META-INF/services/javax.annotation.processing.Processor,并填写注解解析器的类路径,这样在编译的时候就能自动找到解析器
看上去实现编译时注解仍是很容易的,可是真要完整的实现一个相似Butter Knife的框架,这还只是开始。
Butter Knife是专一View的注入,在使用注解的类编译后,查看编译后的class文件,会发现多出了文件,如:
SimpleActivity$$ViewInjector.java
SimpleActivity$$ViewInjector.java,就是经过编译时注解动态建立出来的,查看SimpleActivity$$ViewInjector.java的内容
// Generated code from Butter Knife. Do not modify! package com.example.butterknife; import android.view.View; import butterknife.ButterKnife.Finder; public class SimpleActivity$$ViewInjector { public static void inject(Finder finder, final com.example.butterknife.SimpleActivity target, Object source) { View view; view = finder.findRequiredView(source, 2131230759, "field 'title'"); target.title = (android.widget.TextView) view; view = finder.findRequiredView(source, 2131230783, "field 'subtitle'"); target.subtitle = (android.widget.TextView) view; view = finder.findRequiredView(source, 2131230784, "field 'hello', method 'sayHello', and method 'sayGetOffMe'"); target.hello = (android.widget.Button) view; view.setOnClickListener( new butterknife.internal.DebouncingOnClickListener() { @Override public void doClick( android.view.View p0 ) { target.sayHello(); } }); view.setOnLongClickListener( new android.view.View.OnLongClickListener() { @Override public boolean onLongClick( android.view.View p0 ) { return target.sayGetOffMe(); } }); view = finder.findRequiredView(source, 2131230785, "field 'listOfThings' and method 'onItemClick'"); target.listOfThings = (android.widget.ListView) view; ((android.widget.AdapterView<?>) view).setOnItemClickListener( new android.widget.AdapterView.OnItemClickListener() { @Override public void onItemClick( android.widget.AdapterView<?> p0, android.view.View p1, int p2, long p3 ) { target.onItemClick(p2); } }); view = finder.findRequiredView(source, 2131230786, "field 'footer'"); target.footer = (android.widget.TextView) view; } public static void reset(com.example.butterknife.SimpleActivity target) { target.title = null; target.subtitle = null; target.hello = null; target.listOfThings = null; target.footer = null; } }
inject方法进行初始化,reset进行释放。inject都是调用Finder的方法与android系统的findViewById等方法很像,再来看Finder类,只截取部分。
public enum Finder { public <T> T findRequiredView(Object source, int id, String who) { T view = findOptionalView(source, id, who); if (view == null) { String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '" + name + "' with ID " + id + " for " + who + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'" + " (methods) annotation."); } return view; } public <T> T findOptionalView(Object source, int id, String who) { View view = findView(source, id); return castView(view, id, who); } @Override protected View findView(Object source, int id) { return ((View) source).findViewById(id); } @SuppressWarnings("unchecked") // That's the point. public <T> T castView(View view, int id, String who) { try { return (T) view; } catch (ClassCastException e) { if (who == null) { throw new AssertionError(); } String name = getResourceEntryName(view, id); throw new IllegalStateException("View '" + name + "' with ID " + id + " for " + who + " was of the wrong type. See cause for more info.", e); } }
findRequiredView方法实际上就是咱们经常使用findViewById的实现,其动态帮咱们添加了这些实现。view注入的原理咱们就清楚了。
虽然编译时建立了这个类,运行的时候如何使用这个类呢,这里我用本身实现的一个类来描述
public class XlViewInjector { static final Map<Class<?>, AbstractInjector<Object>> INJECTORS = new LinkedHashMap<Class<?>, AbstractInjector<Object>>(); public static void inject(Activity activity){ AbstractInjector<Object> injector = findInjector(activity); injector.inject(Finder.ACTIVITY, activity, activity); } public static void inject(Object target, View view){ AbstractInjector<Object> injector = findInjector(target); injector.inject(Finder.VIEW, target, view); } private static AbstractInjector<Object> findInjector(Object target){ Class<?> clazz = target.getClass(); AbstractInjector<Object> injector = INJECTORS.get(clazz); if(injector == null){ try{ Class injectorClazz = Class.forName(clazz.getName()+"$$"+ProxyInfo.PROXY); injector = (AbstractInjector<Object>) injectorClazz.newInstance(); INJECTORS.put(clazz, injector); }catch(Exception e){ e.printStackTrace(); } } return injector; } }
XlViewInjector与ButterKnife,好比调用时咱们都会执行XlViewInjector.inject方法,经过传入目标类的名称得到封装后的类实例就是SimpleActivity$$ViewInjector.java,再调用它的inject,来初始化各个view。
总结一下整个实现的流程:
一、经过编译时注解动态建立了一个包装类,在这个类中已解析了注解,实现了获取view、设置监听等代码。
二、执行时调用XlViewInjector.inject(object)方法,实例化object类对应的包装类,并执行他的初始化方法inject;
所以咱们也能明白为何XlViewInjector.inject(object)方法必定要在setContentView以后执行。
解析有用到android api,所以须要建立Android工程,可是android library并无javax的一些功能,
在eclipse环境下,右键build-path add library把jdk加进来
在android studio下,须要先建立java Library功能,实现与view无关的解析,再建立一个android library功能引用这个工程并实现余下的解析。
详细的实现过程能够参考这些博客,描述的都很详细
http://www.trinea.cn/android/java-annotation-android-open-source-analysis/
http://blog.csdn.net/lmj623565791/article/details/43452969
https://segmentfault.com/a/1190000002785541