咱們平常使用的不少開源庫都有註解的實現,主要有運行時註解和編譯時註解兩種。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