編譯時注解
運行時注解和編譯時注解,兩種注解方式對性能的影響是不一樣的。之前看到相關資料,都說對于注解的優(yōu)化,都用的是編譯時注解進行性能的提升。自己在使用的時候也查閱各種博客、第三方庫的代碼,對于編譯時注解的實現原理進行理解以及記錄。
編譯時注解框架基本構成
- compiler
- api
- annotation
compiler
這部分主要是框架所使用的注解處理器(Annotation Processer),用于在編譯時掃描和處理注解。也可以自定義,并且處理自己的注解邏輯。
自定義注解器需要繼承 AbstractProcessor ,并且實現四個方法:
- init
- process
- getSupportedAnnotationTypes
- getSupportedSourceVersion
init 方法
處理器的初始化
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//錯誤處理工具
mMessager = processingEnvironment.getMessager();
//Filer可以創(chuàng)建文件
mFileUtils = processingEnvironment.getFiler();
//Element代表程序的元素,例如包、類、方法。
mElementUtils = processingEnvironment.getElementUtils();
//處理TypeMirror的工具類,用于取類信息
mTypeUtils = env.getTypeUtils();
}
process
/**
* 處理器實際處理邏輯入口
* @param set
* @param roundEnvironment 所有注解的集合
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE,"Start BindProcesser process method!!");
//獲取注解的元素變量
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//循環(huán)處理注解的每一個元素,并且放入一個 map 中
for(Element element : elements){
//校驗元素是否為 VariableElement
if(!(element instanceof VariableElement)){
return false;
}
//轉換變量類型
VariableElement variableElement = (VariableElement)element;
//修飾變量所在的類
TypeElement typeElement = (TypeElement)variableElement.getEnclosingElement();
//使用類的全路徑作為key
String qulifiedName = typeElement.getQualifiedName().toString();
//獲取 map 中是都已經有相關的代理信息
ProxyInfo proxyInfo = mProxyInfoMap.get(qulifiedName);
if(proxyInfo == null){
proxyInfo = new ProxyInfo(typeElement,mElementUtils);
mProxyInfoMap.put(qulifiedName,proxyInfo);
}
//獲取注解
BindView annotation = variableElement.getAnnotation(BindView.class);
//注解上的控件ID
int id = annotation.value();
proxyInfo.injectVariables.put(id,variableElement);
//第二步驟: 遍歷Map生成代理類
for(String key: mProxyInfoMap.keySet()){
ProxyInfo proxyInfo2 = mProxyInfoMap.get(key);
try {
//創(chuàng)建文件對象
JavaFileObject soureFile = mFileUtils.createSourceFile(
proxyInfo2.getProxyClassFullName(), //文件名,全路徑
proxyInfo2.getTypeElement());
//創(chuàng)建寫入對象
Writer writer = soureFile.openWriter();
//寫入內容
writer.write(proxyInfo2.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
以上代碼是參照文章</br>
https://blog.csdn.net/niubitianping/article/details/78492054
處理器的邏輯:
遍歷得到源碼中,需要解析的元素列表。</br>
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);</br>
Processor的過程中,回遍歷所有的java源碼,查找到相關的元素 Element。代碼的每一個部分就相當于一個 Element ,每個Element代表一個靜態(tài)的、語言級別的構件。</br>
PackageElement -- 包 </br>
TypeElement -- 類 </br>
VariableElement -- 變量 </br>
ExecuteableElement -- 方法 </br>-
判斷元素是否可見和符合要求。</br>
獲取所有的Element之后,則開始校驗判斷是否可用或者是符合自己的業(yè)務邏輯。</br>//校驗元素是否為 VariableElement if(!(element instanceof VariableElement)){ return false; } //或者通過如下api校驗元素是否可用 SuperficialValidation.validateElement(element); // 或者檢查元素是否是一個類 if (element.getKind() != ElementKind.CLASS) { ... }
-
組織數據結構得到輸出類參數。
例如:</br>//修飾變量所在的類 TypeElement typeElement = (TypeElement)variableElement.getEnclosingElement(); //使用類的全路徑作為key String qulifiedName = typeElement.getQualifiedName().toString(); //獲取 map 中是都已經有相關的代理信息 ProxyInfo proxyInfo = mProxyInfoMap.get(qulifiedName); if(proxyInfo == null){ proxyInfo = new ProxyInfo(typeElement,mElementUtils); mProxyInfoMap.put(qulifiedName,proxyInfo); } //獲取注解 BindView annotation = variableElement.getAnnotation(BindView.class); //注解上的控件ID int id = annotation.value(); proxyInfo.injectVariables.put(id,variableElement); //第二步驟: 遍歷Map生成代理類 for(String key: mProxyInfoMap.keySet()){ ProxyInfo proxyInfo2 = mProxyInfoMap.get(key); -
輸入生成java文件。
//第二步驟: 遍歷Map生成代理類 for(String key: mProxyInfoMap.keySet()){ ProxyInfo proxyInfo2 = mProxyInfoMap.get(key); try { //創(chuàng)建文件對象 JavaFileObject soureFile = mFileUtils.createSourceFile( proxyInfo2.getProxyClassFullName(), //文件名,全路徑 proxyInfo2.getTypeElement()); //創(chuàng)建寫入對象 Writer writer = soureFile.openWriter(); //寫入內容 writer.write(proxyInfo2.generateJavaCode()); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } }Java代碼生成有兩種方式:</br>
一種是 使用 Filer 進行生成,如下代碼:</br>/** * 生成java文件代碼 * @return */ public String generateJavaCode() { StringBuilder builder = new StringBuilder(); builder.append("http:// Generated code. Do not modify!\n"); builder.append("package ").append(packageName).append(";\n\n"); //注意,這個ImPort的包路徑,是api的包路徑 builder.append("import com.example.qhh_api.*;\n"); builder.append('\n'); builder.append("public class ").append(proxyClassName).append(" implements " + ProxyInfo.PROXY + "<" + mTypeElement.getQualifiedName() + ">"); builder.append(" {\n"); generateMethods(builder); builder.append('\n'); builder.append("}\n"); return builder.toString(); } /** * 生成方法 * @param builder */ private void generateMethods(StringBuilder builder) { builder.append("@Override\n "); builder.append("public void inject(" + mTypeElement.getQualifiedName() + " host, Object source ) {\n"); for (int id : injectVariables.keySet()) { VariableElement element = injectVariables.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append(" if(source instanceof android.app.Activity){\n"); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)source).findViewById( " + id + "));\n"); builder.append("\n}else{\n"); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.view.View)source).findViewById( " + id + "));\n"); builder.append("\n}"); } builder.append(" }\n"); }第二種方式,使用 JavaPoet 庫,導包方式:</br>
compile 'com.squareup:javapoet:1.9.0' </br>
最新 'com.squareup:javapoet:1.11.1'注意:JavaPoet 也是必須在 Java Library 中使用,因為Javax的核心包在 Android module 和 Library中都不存在。Android Library 調用 Java Library中的方法包含 javax 庫文件的會報錯。
MethodSpec methodSpec = MethodSpec.methodBuilder("inject") .addModifiers(Modifier.PRIVATE) .returns(void.class) .addParameter(String.class, "id") .addStatement("String view = id") .build(); TypeSpec typeSpec = TypeSpec.classBuilder("ViewInject") .addModifiers(Modifier.PUBLIC) .addMethod(methodSpec) .build(); JavaFile javaFile = JavaFile.builder("com.sensetime.test", typeSpec) .build(); try { javaFile.writeTo(mFileUtils); } catch (IOException e) { e.printStackTrace(); }JavaPoet 的使用可以參考:</br>
https://github.com/square/javapoet </br>
https://juejin.im/post/584d4b5b0ce463005c5dc444 錯誤處理。
在工程中生成 compiler(Annotation Processer),需要生成 Java Library,而不是 Android Library 。
總結
主要是通過一些網上的Demo,以及ButterKnife的源碼,來理解編譯時注解框架的工作原理。簡單的使用編譯時注解,完成對項目的一些解耦探索。</br>
在使用依賴的時候,出現在Activity中輸入自定義的注解,無法自動提示,只能輸入全稱的問題。找了好多好多參考,最終自己輸入全稱實現,很尷尬。
小小的demo : https://github.com/qinhaihang/AnnotationProcesserDemo