【Android APT】注解处理器 ( 根据注解生成 Java 代码 )
文章目錄
- 一、生成 Java 代碼
- 二、實現(xiàn) IButterKnife 接口
- 三、視圖綁定主要操作
- 四、完整注解處理器代碼
- 五、博客資源
Android APT 學習進階路徑 : 推薦按照順序閱讀 , 從零基礎(chǔ)到開發(fā)簡易 ButterKnife 注解框架的學習路徑 ;
- 【Java 注解】注解簡介及作用
- 【Java 注解】自定義注解 ( 注解屬性定義與賦值 )
- 【Java 注解】自定義注解 ( 元注解 )
- 【Java 注解】自定義注解 ( 注解解析 )
- 【Java 注解】自定義注解 ( 使用注解實現(xiàn)簡單測試框架 )
- 【Android APT】編譯時技術(shù) ( ButterKnife 原理分析 )
- 【Android APT】編譯時技術(shù) ( 編譯時注解 和 注解處理器 依賴庫 )
- 【Android APT】編譯時技術(shù) ( 開發(fā)編譯時注解 )
- 【Android APT】注解處理器 ( 注解標注 與 初始化方法 )
- 【Android APT】注解處理器 ( 配置注解依賴、支持的注解類型、Java 版本支持 )
- 【Android APT】注解處理器 ( Element 注解節(jié)點相關(guān)操作 )
- 【Android APT】注解處理器 ( 生成代碼并自動綁定控件 )
上一篇博客 【Android APT】注解處理器 ( Element 注解節(jié)點相關(guān)操作 )中 對 注解所標注的 節(jié)點 , 進行了獲取及分析 , 將 VariableElement 類型的 注解節(jié)點 , 按照所在 Activity 進行了分組 ;
本篇博客開發(fā) 注解處理器 的 生成代碼部分 ;
一、生成 Java 代碼
上一篇博客 【Android APT】注解處理器 ( Element 注解節(jié)點相關(guān)操作 ) 中已經(jīng)將 注解節(jié)點 , 按照 Activity 分組 , 放在了 HashMap<String, ArrayList<VariableElement>> elementMap 數(shù)據(jù)結(jié)構(gòu)中 , 要生成的 .java 類的個數(shù)就是該 HashMap 鍵值對的個數(shù) ;
目標是生成如下代碼 :
package kim.hsl.apt;import android.view.View;public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {public void bind(kim.hsl.apt.MainActivity target) {target.hello = target.findViewById(2131230899);} }逐個遍歷 HashMap<String, ArrayList<VariableElement>> elementMap 數(shù)據(jù)結(jié)構(gòu) , 要從該 HashMap 中獲取上述要生成代碼的相關(guān)信息 ;
package kim.hsl.apt;
生成上述代碼 , 需要獲取包名 kim.hsl.apt , 根據(jù) VariableElement 注解節(jié)點 , 獲取 TypeElement 父節(jié)點 , 使用 ElementUtils 獲取 TypeElement 節(jié)點對應(yīng)的 PackageElement 包節(jié)點 , 調(diào)用該節(jié)點的 getQualifiedName 方法獲取完整的包名信息 ;
//獲取對應(yīng)類的包名 // 獲取 VariableElement 的父節(jié)點 TypeElement TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();// 獲取 Activity 名稱 String activitySimpleName = typeElement.getSimpleName().toString();// 獲取包節(jié)點 PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);// 獲取包名 String packageName = packageElement.getQualifiedName().toString();public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {
生成上述代碼 , 需要獲取類名 , 以及完整的包名 和 類名 ; 調(diào)用 TypeElement 的 getSimpleName 方法 , 可以獲取不帶包名的類名 ;
// 獲取類名 String className = activitySimpleName + "_ViewBinder";public void bind(kim.hsl.apt.MainActivity target) {target.hello = target.findViewById(2131230899);} }
生成上述代碼 , 其中 target.hello = target.findViewById(2131230899); 代碼需要循環(huán)生成 , 該 Activity 中有多少變量添加了 @BindView 注解 , 就需要有幾行上述代碼 ;
// public void bind(kim.hsl.apt.MainActivity target){ stringBuffer.append("public void bind(" + packageName + "." + activitySimpleName + " target){\n");for (VariableElement variableElement : variableElements){// 循環(huán)被注解的字段// 為每個 VariableElement 注解字段生成 target.findViewById(R.id.xxx); 代碼// 獲取成員變量名String variableName = variableElement.getSimpleName().toString();// 獲取資源 id , 通過注解 , 獲取注解屬性 valueint resourceId = variableElement.getAnnotation(BindView.class).value();// target.stringBuffer.append("target." + variableName + " = target.findViewById(" + resourceId + ");\n"); }// } stringBuffer.append("}\n"); // } stringBuffer.append("}\n");二、實現(xiàn) IButterKnife 接口
該接口直接定義在主應(yīng)用 , 上面的 注解處理器 本質(zhì)上就是在 編譯時 生成該接口的實現(xiàn)類 , 并實現(xiàn)了其中的 bind 方法 , 每個 Activity 界面都要 生成一個該接口的子類對象 , 在該 生成的 IButterKnife 子類中進行 組件的 findViewById 的視圖綁定操作 ;
package kim.hsl.apt;public interface IButterKnife<T> {void bind(T target); }嚴謹一點的話 , 該接口一般是定義在 Android 依賴庫 中 ;
三、視圖綁定主要操作
在 Activity 界面中 , 調(diào)用
ButterKnife.bind(this);方法 , 即可實現(xiàn)視圖綁定操作 , 實際上是通過 Activity 的類名 “MainActivity” , 獲取到生成的類名 “MainActivity_ViewBinder” , 通過反射獲取該類對象 ;
直接創(chuàng)建該對象 , 并調(diào)用對象的 bind 方法 , 即可完成視圖綁定 ;
ButterKnife 及靜態(tài) bind 方法實現(xiàn) :
package kim.hsl.apt;public class ButterKnife {/*** 在 Activity 中調(diào)用該方法, 綁定接口* @param target*/public static void bind(Object target){String className = target.getClass().getName() + "_ViewBinder";try {// 通過反射得到 MainActivity_ViewBinder 類對象Class<?> clazz = Class.forName(className);// 調(diào)用生成的代碼 MainActivity_ViewBinder 的 bind 方法if (IButterKnife.class.isAssignableFrom(clazz)){IButterKnife iButterKnife = (IButterKnife) clazz.newInstance();iButterKnife.bind(target);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}} }四、完整注解處理器代碼
package kim.hsl.annotation_compiler;import com.google.auto.service.AutoService;import java.io.File; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.tools.Diagnostic; import javax.tools.JavaFileObject;import kim.hsl.annotation.BindView;/*** 生成代碼的注解處理器*/ @AutoService(Processor.class) public class Compiler extends AbstractProcessor {/*** 生成 Java 代碼對象*/private Filer mFiler;/*** 日志打印*/private Messager mMessager;/*** 初始化注解處理器相關(guān)工作* @param processingEnv*/@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);this.mFiler = processingEnv.getFiler();this.mMessager = processingEnv.getMessager();}/*** 聲明 注解處理器 要處理的注解類型* @return*/@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> supportedAnnotationTypes = new HashSet<String>();// 將 BindView 全類名 kim.hsl.annotation.BinndView 放到 Set 集合中supportedAnnotationTypes.add(BindView.class.getCanonicalName());return supportedAnnotationTypes;}/*** 聲明支持的 JDK 版本* @return*/@Overridepublic SourceVersion getSupportedSourceVersion() {// 通過 ProcessingEnvironment 類獲取最新的 Java 版本并返回return processingEnv.getSourceVersion();}/*** 搜索 Android 代碼中的 BindView 注解* 并生成相關(guān)代碼* @param annotations* @param roundEnv* @return*/@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {// 搜索 BindView , 將 BindView 注解放在什么元素上 , 得到的就是相應(yīng)類型的元素// 根據(jù) 注解類型 獲取 被該注解類型 標注的元素 , 元素可能是類 , 方法 , 字段 ;// 通過 getElementsAnnotatedWith 方法可以搜索到整個 Module 中所有使用了 BindView 注解的元素Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);// @BindView 注解標注的都是 Activity 中的成員字段,// 上述 elements 中的元素都是 VariableElement 類型的節(jié)點HashMap<String, ArrayList<VariableElement>> elementMap = new HashMap<>();// 遍歷 elements 注解節(jié)點 , 為節(jié)點分組for (Element element : elements){// 將注解節(jié)點類型強轉(zhuǎn)為 VariableElement 類型VariableElement ve = (VariableElement) element;// 獲取該注解節(jié)點對應(yīng)的成員變量類名// 先獲取該注解節(jié)點的上一級節(jié)點 , 注解節(jié)點是 VariableElement , 成員字段節(jié)點// 上一級節(jié)點是就是 Activity 類節(jié)點對應(yīng)的 類節(jié)點 TypeElementTypeElement te = (TypeElement) ve.getEnclosingElement();// 獲取 Activity 的全類名String activityFullName = te.getQualifiedName().toString();mMessager.printMessage(Diagnostic.Kind.NOTE, "TypeElement : " + activityFullName + " , VariableElement : " + ve.getSimpleName());// 獲取 elementMap 集合中的 Activity 的全類名對應(yīng)的 VariableElement 節(jié)點集合// 如果是第一次獲取 , 為空 ,// 如果之前已經(jīng)獲取了該 Activity 的全類名對應(yīng)的 VariableElement 節(jié)點集合, 那么不為空ArrayList<VariableElement> variableElements = elementMap.get(activityFullName);if (variableElements == null){variableElements = new ArrayList<>();// 創(chuàng)建之后 , 將集合插入到 elementMap 集合中elementMap.put(activityFullName, variableElements);}// 將本節(jié)點插入到 HashSet<VariableElement> variableElements 集合中variableElements.add(ve);}// 生成代碼// 遍歷 HashMap<String, HashSet<VariableElement>> elementMap 集合// 獲取 Key 的迭代器Iterator<String> iterator = elementMap.keySet().iterator();while (iterator.hasNext()){// 獲取 Activity 全類名String key = iterator.next();// 獲取 Activity 下被注解標注的 VariableElement 注解節(jié)點ArrayList<VariableElement> variableElements = elementMap.get(key);//獲取對應(yīng)類的包名// 獲取 VariableElement 的父節(jié)點 TypeElementTypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();// 獲取 Activity 名稱String activitySimpleName = typeElement.getSimpleName().toString();// 獲取包節(jié)點PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);// 獲取包名String packageName = packageElement.getQualifiedName().toString();// 獲取類名String className = activitySimpleName + "_ViewBinder";// 寫出文件的字符流Writer writer = null;// 獲取到包名后 , 開始生成 Java 代碼try {mMessager.printMessage(Diagnostic.Kind.NOTE, "Create Java Class Name : " + packageName + "." + className);// 根據(jù) 包名.類名_ViewBinder 創(chuàng)建 Java 文件JavaFileObject javaFileObject = mFiler.createSourceFile(packageName + "." + className);// 生成 Java 代碼writer = javaFileObject.openWriter();// 生成字符串文本緩沖區(qū)StringBuffer stringBuffer = new StringBuffer();// 逐行寫入文本到緩沖區(qū)中// package kim.hsl.apt;stringBuffer.append("package " + packageName +";\n");// import android.view.View;stringBuffer.append("import android.view.View;\n");// public class MainActivity_ViewBinding implements IButterKnife<kim.hsl.apt.MainActivity>{stringBuffer.append("public class " + className + " implements IButterKnife<" + packageName + "." + activitySimpleName +">{\n");//stringBuffer.append("public class " + className +"{\n");// public void bind(kim.hsl.apt.MainActivity target){stringBuffer.append("public void bind(" + packageName + "." + activitySimpleName + " target){\n");for (VariableElement variableElement : variableElements){// 循環(huán)被注解的字段// 為每個 VariableElement 注解字段生成 target.findViewById(R.id.xxx); 代碼// 獲取成員變量名String variableName = variableElement.getSimpleName().toString();// 獲取資源 id , 通過注解 , 獲取注解屬性 valueint resourceId = variableElement.getAnnotation(BindView.class).value();// target.stringBuffer.append("target." + variableName + " = target.findViewById(" + resourceId + ");\n");}// }stringBuffer.append("}\n");// }stringBuffer.append("}\n");mMessager.printMessage(Diagnostic.Kind.NOTE, "stringBuffer.toString() : " + stringBuffer.toString());mMessager.printMessage(Diagnostic.Kind.NOTE, "writer : " + writer);// 將字符串緩沖區(qū)的數(shù)據(jù)寫出到 Java 文件中writer.write(stringBuffer.toString());mMessager.printMessage(Diagnostic.Kind.NOTE,"write finished");} catch (Exception e) {mMessager.printMessage(Diagnostic.Kind.NOTE,"IOException");e.printStackTrace();}finally {if (writer != null){try {mMessager.printMessage(Diagnostic.Kind.NOTE,"write closed");writer.close();} catch (IOException e) {e.printStackTrace();}}}}mMessager.printMessage(Diagnostic.Kind.NOTE,"process finished");return false;} }
五、博客資源
博客源碼 :
-
GitHub : https://github.com/han1202012/APT
-
CSDN : https://download.csdn.net/download/han1202012/18917831
總結(jié)
以上是生活随笔為你收集整理的【Android APT】注解处理器 ( 根据注解生成 Java 代码 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【错误记录】Android 注解处理器报
- 下一篇: 【Android 文件管理】分区存储 (