前言
Butterknife我相信,對大部分做Android開發的人都不陌生,這個是供職于Square公司的JakeWharton大神開發的,目前github的star為 ~12449~ 。使用這個庫,在AS中搭配Android ButterKnife Zelezny插件,簡直是開發神器,從此擺脫繁瑣的 findViewById(int id)
,也不用自己手動 @bind(int id)
, 直接用插件生成即可。這種采用注解DI組件的方式,在Spring中很常見,起初也是在Spring中興起的 。今天我們就一探究竟,自己實現一個butterknife (有不會用的,請自行Google)
。
項目地址: JakeWharton/butterknife
butterknife
實現原理 (假定你對注解有一定的了解)
注解
對ButterKnife有過了解人 , 注入字段的方式是使用注解 @Bind(R.id.tv_account_name)
,但首先我們需要在 Activity
聲明注入 ButterKnife.bind(Activity activity)
。我們知道,注解分為好幾類, 有在源碼生效的注解,有在類文件生成時生效的注解,有在運行時生效的注解。分別為 RetentionPolicy.SOURCE
, RetentionPolicy.CLASS
, RetentionPolicy.RUNTIME
,其中以 RetentionPolicy.RUNTIME
最為消耗性能。而ButterKnife使用的則是編譯器時期注入,在使用的時候,需要配置 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
, 這個配置說明,在編譯的時候,進行注解處理。要對注解進行處理,則需要繼承 AbstractProcessor
, 在 boolean process(Setlt;? extends TypeElementgt; annotations, RoundEnvironment roundEnv)
中進行注解處理。
實現方式
知曉了注解可以在編譯的時候進行處理,那么,我們就可以得到注解的字段屬性與所在類 , 進而生成注入文件,生成一個注入類的內部類,再進行字段處理 , 編譯之后就會合并到注入類中,達到植入新代碼段的目的。例如:我們注入 @VInjector(R.id.tv_show) TextView tvShow;
我們就可以得到 tvShow
這個變量與 R.id.tv_show
這個id的值,然后進行模式化處理 injectObject.tvShow = injectObject.findViewById(R.id.tv_show);
,再將代碼以內部類的心事加入到組件所在的類中 , 完成一次DI(注入) 。
實現流程圖
view_injector_流程圖
① 首先創建一個視圖注解
② 創建一個注解處理器,用來得到注解的屬性與所屬類
③ 解析注解,分離組合Class與屬性
④ 組合Class與屬性,生成新的Java File
項目UML圖
ViewInject UML
簡要說明:
主要類:
VInjectProcessor
----gt; 注解處理器 , 需要配置注解處理器
resources - META-INF - services - javax.annotation.processing.Processor
Processor內容:
com.zeno.viewinject.apt.VInjectProcessor # 指定處理器全類名
圖示:
processor config
VInjectHandler
----gt; 注解處理類 , 主要進行注入類與注解字段進行解析與封裝,將同類的字段使用map集合進行映射。exp: Maplt;Class,Listlt;Attrgt;gt; 。
ViewGenerateAdapter
-----gt; Java File 生成器,將注入的類與屬性,重新生成一個Java File,是其注入類的內部類 。
具體實現
一 , 創建注解 , 對視圖進行注解,R.id.xxx , 所以注解類型是int類型
/** * Created by Zeno on 2016/10/21. * * View inject * 字段注入注解,可以新建多個注解,再通過AnnotationProcessor進行注解處理 * RetentionPolicy.CLASS ,在編譯的時候進行注解 。我們需要在生成.class文件的時候需要進行處理 */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface VInjector { int value(); }
二, 注解處理器 關于注解處理器配置,上面已經做了說明
/** * Created by Zeno on 2016/10/21. * * Inject in View annotation processor * * 需要在配置文件中指定處理類 resources/META-INF/services/javax.annotation.processing.Processor * com.zeno.viewinject.apt.VInjectProcessor */ @SupportedAnnotationTypes(quot;com.zeno.viewinject.annotation.VInjectorquot;) @SupportedSourceVersion(SourceVersion.RELEASE_6) public class VInjectProcessor extends AbstractProcessor { Listlt;IAnnotationHandlergt; mAnnotationHandler = new ArrayListlt;gt;(); Maplt;String,Listlt;VariableElementgt;gt; mHandleAnnotationMap = new HashMaplt;gt;(); private IGenerateAdapter mGenerateAdapter; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); // init annotation handler , add handler registerHandler(new VInjectHandler()); // init generate adapter mGenerateAdapter = new ViewGenerateAdapter(processingEnv); } /*可以有多個處理*/ protected void registerHandler(IAnnotationHandler handler) { mAnnotationHandler.add(handler); } // annotation into process run @Override public boolean process(Setlt;? extends TypeElementgt; annotations, RoundEnvironment roundEnv) { for (IAnnotationHandler handler : mAnnotationHandler) { // attach environment , 關聯環境 handler.attachProcessingEnvironment(processingEnv); // handle annotation 處理注解 ,得到注解類的屬性列表 mHandleAnnotationMap.putAll(handler.handleAnnotation(roundEnv)); } // 生成輔助類 mGenerateAdapter.generate(mHandleAnnotationMap); // 表示處理 return true; } }
對得到的注解進行處理 , 主要是進行注解類型與屬性進行分離合并處理,因為一個類有多個屬性,所以采用map集合,進行存儲,數據結構為:Maplt;String:className , Listlt;VariableElement:elementgt;gt;
/** * Created by Zeno on 2016/10/21. * * 注解處理實現 , 解析VInjector注解屬性 */ public class VInjectHandler implements IAnnotationHandler { private ProcessingEnvironment mProcessingEnvironment; @Override public void attachProcessingEnvironment(ProcessingEnvironment environment) { this.mProcessingEnvironment = environment; } @Override public Maplt;String, Listlt;VariableElementgt;gt; handleAnnotation(RoundEnvironment roundEnvironment) { Maplt;String,Listlt;VariableElementgt;gt; map = new HashMaplt;gt;(); /*獲取一個類中帶有VInjector注解的屬性列表*/ Setlt;? extends Elementgt; elements = roundEnvironment.getElementsAnnotatedWith(VInjector.class); for (Element element : elements) { VariableElement variableElement = (VariableElement) element; /*獲取類名 ,將類目與屬性配對,一個類,對于他的屬性列表*/ String className = getFullClassName(variableElement); Listlt;VariableElementgt; cacheElements = map.get(className); if (cacheElements == null) { cacheElements = new ArrayListlt;gt;(); map.put(className,cacheElements); } cacheElements.add(variableElement); } return map; } /** * 獲取注解屬性的完整類名 * @param variableElement */ private String getFullClassName(VariableElement variableElement) { TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement(); String packageName = AnnotationUtils.getPackageName(mProcessingEnvironment,typeElement); return packageName quot;.quot; typeElement.getSimpleName().toString(); } }
生成Java File , 根據獲取的屬性與類,創建一個注入類的內部類
/** * Created by Zeno on 2016/10/21. * * 生成View注解輔助類 */ public class ViewGenerateAdapter extends AbstractGenerateAdapter { public ViewGenerateAdapter(ProcessingEnvironment processingEnvironment) { super(processingEnvironment); } @Override protected void generateImport(Writer writer, InjectInfo injectInfo) throws IOException { writer.write(quot;package quot; injectInfo.packageName quot;;quot;); writer.write(quot;\n\nquot;); writer.write(quot;import com.zeno.viewinject.adapter.IVInjectorAdapter;quot;); writer.write(quot;\n\nquot;); writer.write(quot;import com.zeno.viewinject.utils.ViewFinder;quot;); writer.write(quot;\n\n\nquot;); writer.write(quot;/* This class file is generated by ViewInject , do not modify */quot;); writer.write(quot;\nquot;); writer.write(quot;public class quot; injectInfo.newClassName quot; implements IVInjectorAdapterlt;quot; injectInfo.className quot;gt; {quot;); writer.write(quot;\n\nquot;); writer.write(quot;public void injects(quot; injectInfo.className quot; target) {quot;); writer.write(quot;\nquot;); } @Override protected void generateField(Writer writer, VariableElement variableElement, InjectInfo injectInfo) throws IOException { VInjector vInjector = variableElement.getAnnotation(VInjector.class); int resId = vInjector.value(); String fieldName = variableElement.getSimpleName().toString(); writer.write(quot;\t\ttarget.quot; fieldName quot; = ViewFinder.findViewById(target,quot; resId quot;);quot;); writer.write(quot;\nquot;); } @Override protected void generateFooter(Writer writer) throws IOException { writer.write(quot; \t}quot;); writer.write(quot;\n\nquot;); writer.write(quot;}quot;); } }
結語
ButterKnife類型的注解框架,其主要核心就是編譯時期注入, 如果是采用運行時注解的話,那性能肯定影響很大,國內有些DI框架就是采用的運行時注解,所以性能上會有所損傷 。原以為很高深的東西,其實剖析過原理之后,也就漸漸明白了,不再視其為高深莫測,我們自己也可以實現同等的功能。
程序員最好的學習方式就是,學習別人的代碼,特別是像jakeWharton這樣的大神的代碼,值得研究與學習 , 然后模仿之。
源碼
ViewInjectDemo UML圖與流程圖都會放在github上
Tags: ButterKnife
文章來源:http://www.jianshu.com/p/003be1b75e28