Android中注解處理器APT用法示例
APT,Annotation Processing Tool,即注解處理器,是一種用來處理注解的工具,常用在編譯時掃描和處理注解,最終生成處理注解邏輯的Java文件。APT技術(shù)在目前的很多框架上都有著使用,如ButterKnife,ARouter,GreenDAO等框架上都有著APT技術(shù)的影子。
APT作用
使用APT可以在編譯時來處理編譯時注解,生成額外的Java文件,有如下效果:
可以達(dá)到減少重復(fù)代碼手工編寫的效果。
如ButterKnife,我們可以直接使用注解來減少findviewbyid這些代碼,只需要通過注解表示是哪個id就夠了。
功能封裝。將主要的功能邏輯封裝起來,只保留注解調(diào)用。
相對于使用Java反射來處理運行時注解,使用APT有著更加良好的性能。
Android基本編譯流程
Android中的代碼編譯時需要經(jīng)過:Java——>class ——> dex 流程,代碼最終生成dex文件打入到APK包里面。
APT是在編譯開始時就介入的,用來處理編譯時注解。
AOP(Aspect Oridnted Programming)是在編譯完成后生成dex文件之前,通過直接修改.class文件的方式,來對代碼進(jìn)行修改或添加邏輯。常用在在代碼監(jiān)控,代碼修改,代碼分析這些場景。
APT基本使用
基本使用流程主要包括如下幾個步驟:
- 創(chuàng)建自定義注解
- 創(chuàng)建注解處理器,處理Java文件生成邏輯
- 封裝一個供外部調(diào)用的API
- 項目中調(diào)用
本次以仿寫B(tài)utterKnife綁定View為例,省略findviewbyId的代碼,調(diào)用代碼如下:
public class AnnotationTestActivity extends AppCompatActivity { @BindView(R.id.tv_annotation_test1) TextView tvAnnotationTest1; @BindView(R.id.tv_annotation_test2) TextView tvAnnotationTest2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_annotation_test); BindViewTools.bind(this); tvAnnotationTest1.setText("測試文本"); tvAnnotationTest1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Logger.toast(WorkDemoApplication.context,"控件1:"+R.id.tv_annotation_test1); } }); tvAnnotationTest2.setText("另一個文本"); tvAnnotationTest2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Logger.toast(WorkDemoApplication.context,"控件2:"+R.id.tv_annotation_test2); } }); } }
1、 自定義注解
我們新建立一個Java lib,命名為annotationTest
,用來承載自定義注解,代碼如下所示:
@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
2、注解處理器
額外新建立一個Java lib,命名為processorTest
,用來承載注解處理及Java文件生成邏輯。
主要包括如下幾個步驟:
- 添加注解處理器
- 注解處理器注冊
- 添加java代碼生成邏輯
需要注意的是,當(dāng)前的注解處理器lib需要引入注解lib——annotationTest
,在當(dāng)前Module的build.gradle
文件中配置:
implementation project(':annotationTest')//依賴剛剛創(chuàng)建的annotation模塊
注解處理器
注解處理器代碼如下:
public class BindViewProcessor extends AbstractProcessor { private Messager mMessager; private Elements mElementUtils; private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mMessager = processingEnv.getMessager(); mElementUtils = processingEnv.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> supportTypes = new LinkedHashSet<>(); supportTypes.add(BindView.class.getCanonicalName()); return supportTypes; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { mMessager.printMessage(Diagnostic.Kind.NOTE, "processing..."); mProxyMap.clear(); //得到所有的注解 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); for (Element element : elements) { VariableElement variableElement = (VariableElement) element; TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); String fullClassName = classElement.getQualifiedName().toString(); ClassCreatorProxy proxy = mProxyMap.get(fullClassName); if (proxy == null) { proxy = new ClassCreatorProxy(mElementUtils, classElement); mProxyMap.put(fullClassName, proxy); } BindView bindAnnotation = variableElement.getAnnotation(BindView.class); int id = bindAnnotation.value(); proxy.putElement(id, variableElement); } //通過遍歷mProxyMap,創(chuàng)建java文件 for (String key : mProxyMap.keySet()) { ClassCreatorProxy proxyInfo = mProxyMap.get(key); JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build(); try { //生成Java文件 javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + "error"); } } mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ..."); return true; } }
此處為了代碼演示簡單起見,并沒有加入格式校驗(如對注解修飾的類型等信息進(jìn)行校驗),如果你實際運用APT技術(shù),請務(wù)必要對注解的使用規(guī)則進(jìn)行詳細(xì)的校驗。
注解處理器注冊
自定義的注解處理器必須經(jīng)過注冊才能夠使用,即需要對注解處理器添加自動主動的注解。
我們可以使用Google autoService來進(jìn)行注解處理器的自動注冊,首先需要在注解處理器所在的module的build.gradle
文件添加autoService的包引入:
//google autoService implementation "com.google.auto.service:auto-service:1.0-rc4" annotationProcessor "com.google.auto.service:auto-service:1.0-rc4"
然后將自動注冊的注解添加到注解處理器上以實現(xiàn)自動注冊效果:
@AutoService(Processor.class) public class BindViewProcessor extends AbstractProcessor { ... }
java代碼生成
對于java代碼的生成存在有多種方式,如字符串拼接,JavaPoet等。
如果要使用JavaPoet,則需要在當(dāng)前的Module的build.gradle
文件中引入:
//javaPoet implementation "com.squareup:javapoet:1.13.0"
詳細(xì)代碼如下:
package com.example.shapetest.bindview; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import java.util.HashMap; import java.util.Map; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; public class ClassCreatorProxy { private String mBindingClassName; private String mPackageName; private TypeElement mTypeElement; private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>(); public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) { this.mTypeElement = classElement; PackageElement packageElement = elementUtils.getPackageOf(mTypeElement); String packageName = packageElement.getQualifiedName().toString(); String className = mTypeElement.getSimpleName().toString(); this.mPackageName = packageName; this.mBindingClassName = className + "_ViewBinding"; } public void putElement(int id, VariableElement element) { mVariableElementMap.put(id, element); } /** * 創(chuàng)建Java代碼 字符串拼接方式 * @return */ public String generateJavaCode() { StringBuilder builder = new StringBuilder(); builder.append("package ").append(mPackageName).append(";\n\n"); builder.append("import com.example..*;\n"); builder.append('\n'); builder.append("public class ").append(mBindingClassName); builder.append(" {\n"); generateMethods(builder); builder.append('\n'); builder.append("}\n"); return builder.toString(); } /** * 加入Method 字符串拼接方式 * @param builder */ private void generateMethods(StringBuilder builder) { builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n"); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n"); } builder.append(" }\n"); } public String getProxyClassFullName() { return mPackageName + "." + mBindingClassName; } public TypeElement getTypeElement() { return mTypeElement; } /** * 創(chuàng)建Java代碼 javapoet * @return */ public TypeSpec generateJavaCode2() { TypeSpec bindingClass = TypeSpec //class名稱設(shè)置 .classBuilder(mBindingClassName) //類為public .addModifiers(Modifier.PUBLIC) .addMethod(generateMethods2()) .build(); return bindingClass; } /** * 加入Method javapoet */ private MethodSpec generateMethods2() { ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString()); MethodSpec.Builder methodBuilder = MethodSpec //方法名稱 .methodBuilder("bind") //方法為public .addModifiers(Modifier.PUBLIC) //返回值 .returns(void.class) //方法參數(shù) .addParameter(host, "host"); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n"); } return methodBuilder.build(); } public String getPackageName() { return mPackageName; } }
3. 對外調(diào)用
在完成了上述兩步之后,APT的實際工作已經(jīng)能夠正常運行了,下面我們實現(xiàn)以下調(diào)用方法,以達(dá)到模仿butterknife的調(diào)用效果。
首先我們需要新建立一個Android lib的Module,命名為apt_lib。
當(dāng)前的對外lib需要引用注解處理器lib——processorTest
,在當(dāng)前Module的build.gradle
文件中進(jìn)行如下配置:
implementation project(path: ':processorTest')
新建對外的調(diào)用方法,代碼如下:、
public class BindViewTools { public static void bind(Activity activity) { Class clazz = activity.getClass(); try { Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding"); Method method = bindViewClass.getMethod("bind", activity.getClass()); method.invoke(bindViewClass.newInstance(), activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
其主要功能是為被注解綁定的Activity生成對應(yīng)的輔助類以實現(xiàn)apt被調(diào)用的效果。
4. 調(diào)用
在進(jìn)行了如下幾步之后,我們實際上已經(jīng)能夠正常的調(diào)用@BindView
這個注解了.
我們在我們的主Module——app
中調(diào)用APT,當(dāng)然此時的APT需要引入之前的lib工程,可以在app的build.gradle
文件中進(jìn)行如下配置:
implementation project(':annotationTest') // 添加依賴模塊 implementation project(':apt_lib') // 添加依賴模塊 implementation project(':processorTest') // 添加依賴模塊 annotationProcessor project(':processorTest')
需要注意的是annotationProcessor這一注解處理工具是Java語言使用的,如果你使用的是kotlin語言,則需要使用kapt.
此時直接調(diào)用我們在開頭的調(diào)用代碼,在編譯執(zhí)行后能夠正常運行。
當(dāng)我們查看工程內(nèi)的build
文件夾時能夠在如下路徑發(fā)現(xiàn)我們之前生成的文件:
看到里面的代碼如下:
package com.example.workdemo.activity; public class AnnotationTestActivity_ViewBinding { public void bind(AnnotationTestActivity host) { host.tvAnnotationTest1 = (android.widget.TextView)(((android.app.Activity)host).findViewById( 2131231102)); host.tvAnnotationTest2 = (android.widget.TextView)(((android.app.Activity)host).findViewById( 2131231103)); } }
這樣的話我們就完成了APT的一個簡單示例。
總結(jié)
本文主要對Android中的APT技術(shù)進(jìn)行了簡單說明。APT技術(shù)能夠利用編譯的技術(shù)直接生成Java邏輯代碼,在代碼重復(fù)度較高的場景中比較適用。
到此這篇關(guān)于Android中注解處理器APT用法示例的文章就介紹到這了,更多相關(guān)Android中注解處理器APT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實現(xiàn)通過手勢控制圖片大小縮放的方法
這篇文章主要介紹了Android實現(xiàn)通過手勢控制圖片大小縮放的方法,結(jié)合實例形式分析了Android控制圖片縮放的原理、實現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-10-10Android 通過當(dāng)前經(jīng)緯度獲得城市的實例代碼
Android 通過當(dāng)前經(jīng)緯度獲得城市的實例代碼,需要的朋友可以參考一下2013-06-06Android開發(fā)之滑動圖片輪播標(biāo)題焦點
這篇文章主要介紹了Android開發(fā)之滑動圖片輪播標(biāo)題焦點的相關(guān)資料,需要的朋友可以參考下2016-05-05android中colors.xml顏色設(shè)置資源文件的方法
這篇文章主要介紹了android中colors.xml顏色設(shè)置資源文件,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03Android 使用Glide加載網(wǎng)絡(luò)圖片等比例縮放的實現(xiàn)方法
這篇文章主要介紹了Android 使用Glide加載網(wǎng)絡(luò)圖片等比例縮放的實現(xiàn)方法,需要的朋友可以參考下2018-08-08