Butterknife原理分析及自己實現Butternife
前言
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有過了解人 , 注入欄位的方式是使用註解@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(Set<? extends TypeElement> 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(注入) 。
實現流程圖
① 首先建立一個檢視註解
② 建立一個註解處理器,用來得到註解的屬性與所屬類
③ 解析註解,分離組合Class與屬性
④ 組合Class與屬性,生成新的Java File
APT生成的Java File , 以及模式程式碼
使用Javac , 編譯時期生成注入類的子類
專案UML圖
簡要說明:
主要類:VInjectProcessor
—-> 註解處理器 , 需要配置註解處理器
resources
- META-INF
- services
- javax.annotation.processing.Processor
Processor內容:
com.zeno.viewinject.apt.VInjectProcessor # 指定處理器全類名
圖示:
VInjectHandler
—-> 註解處理類 , 主要進行注入類與註解欄位進行解析與封裝,將同類的欄位使用map集合進行對映。exp: Map<Class,List<Attr>> 。
ViewGenerateAdapter
—–> 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("com.zeno.viewinject.annotation.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {
List<IAnnotationHandler> mAnnotationHandler = new ArrayList<>();
Map<String,List<VariableElement>> mHandleAnnotationMap = new HashMap<>();
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(Set<? extends TypeElement> 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集合,進行儲存,資料結構為:Map<String:className , List<VariableElement:element>>
/**
* 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 Map<String, List<VariableElement>> handleAnnotation(RoundEnvironment roundEnvironment) {
Map<String,List<VariableElement>> map = new HashMap<>();
/*獲取一個類中帶有VInjector註解的屬性列表*/
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(VInjector.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
/*獲取類名 ,將類目與屬性配對,一個類,對於他的屬性列表*/
String className = getFullClassName(variableElement);
List<VariableElement> cacheElements = map.get(className);
if (cacheElements == null) {
cacheElements = new ArrayList<>();
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+"."+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("package "+injectInfo.packageName+";");
writer.write("
");
writer.write("import com.zeno.viewinject.adapter.IVInjectorAdapter;");
writer.write("
");
writer.write("import com.zeno.viewinject.utils.ViewFinder;");
writer.write("
");
writer.write("/* This class file is generated by ViewInject , do not modify */");
writer.write("
");
writer.write("public class "+injectInfo.newClassName+" implements IVInjectorAdapter<"+injectInfo.className+"> {");
writer.write("
");
writer.write("public void injects("+injectInfo.className+" target) {");
writer.write("
");
}
@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(" target."+fieldName+" = ViewFinder.findViewById(target,"+resId+");");
writer.write("
");
}
@Override
protected void generateFooter(Writer writer) throws IOException {
writer.write(" }");
writer.write("
");
writer.write("}");
}
}
結語
ButterKnife型別的註解框架,其主要核心就是編譯時期注入, 如果是採用執行時註解的話,那效能肯定影響很大,國內有些DI框架就是採用的執行時註解,所以效能上會有所損傷 。原以為很高深的東西,其實剖析過原理之後,也就漸漸明白了,不再視其為高深莫測,我們自己也可以實現同等的功能。
程式設計師最好的學習方式就是,學習別人的程式碼,特別是像jakeWharton這樣的大神的程式碼,值得研究與學習 , 然後模仿之。
原始碼
ViewInjectDemo UML圖與流程圖都會放在github上
原文連結:http://www.jianshu.com/p/003be1b75e28
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。
相關文章
- ButterKnife原理分析
- 一步一步,實現自己的ButterKnife(二)
- 一步一步,實現自己的ButterKnife(一)
- HashMap實現原理及原始碼分析HashMap原始碼
- butterknife原始碼簡單分析&原理簡述原始碼
- Java JDK 動態代理使用及實現原理分析JavaJDK
- Android 熱更新實現原理及程式碼分析Android
- CRC原理及實現
- AOP如何實現及實現原理
- JLRoutes 實現原理分析
- Java JDK 動態代理(AOP)使用及實現原理分析JavaJDK
- Promise原理探究及實現Promise
- KVO使用及實現原理
- Go channel 實現原理分析Go
- Java-JDK動態代理(AOP)使用及實現原理分析JavaJDK
- 外網訪問內網應用原理分析及實現內網
- 淺析Ajax跨域原理及JQuery中的實現分析跨域jQuery
- vue 實現原理及簡單示例實現Vue
- 【linux】helloword原理分析及實戰Linux
- 如何自己實現一個 mobx – 原理解析
- 自己簡單實現Spring的IOC原理Spring
- 如何自己實現一個 mobx - 原理解析
- DES原理及程式碼實現
- TreeMap原理實現及常用方法
- NNLM原理及Pytorch實現PyTorch
- MapReduce原理及簡單實現
- SpringMVC實現原理及解析SpringMVC
- BloomFilter 原理,實現及優化OOMFilter優化
- LRU cache原理及go實現Go
- Java ArrayDeque工作原理及實現Java
- HTTP 代理原理及實現(2)HTTP
- Java HashMap工作原理及實現JavaHashMap
- Android註解使用之通過annotationProcessor註解生成程式碼實現自己的ButterKnife框架Android框架
- Redis GEO & 實現原理深度分析Redis
- 富集分析的原理與實現
- Java原子類實現原理分析Java
- 分析ReentrantLock的實現原理ReentrantLock
- 實現自己的Vue Router -- Vue Router原理解析Vue