注解APT應(yīng)用詳解(手把手教你寫B(tài)utterKnife工具)
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」,作者Android開發(fā)編程。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號(hào)。
一、APT是什么?有什么用,帶著疑惑來學(xué)習(xí)
- APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具,確切的說它是javac的一個(gè)工具,它用來在編譯時(shí)掃描和處理注解。注解處理器以Java代碼(或者編譯過的字節(jié)碼)作為輸入,生成.java文件作為輸出;
- 簡單來說就是在編譯期,通過注解生成.java文件;
- 使用APT的優(yōu)點(diǎn)就是方便、簡單,可以少些很多重復(fù)的代碼;用過ButterKnife、Dagger、EventBus等注解框架的同學(xué)就能感受到,利用這些框架可以少些很多代碼,只要寫一些注解就可以了,他們不過是通過注解,幫助生成了一些高效代碼;
二、APT應(yīng)用-仿照ButterKnife寫個(gè)注解
通過APT實(shí)現(xiàn)一個(gè)功能,通過對(duì)View變量的注解,實(shí)現(xiàn)View的綁定
1、創(chuàng)建幾個(gè)Library來聲明
- Android Library:aptlibs 正常的寫Android的lib
- Java or Kotlin Library:aptlib-anno (專門放我們編寫的注解)
- Java or Kotlin Library :aptlib-processor (編寫動(dòng)態(tài)生成文件的邏輯)
- aptlibs
- plugins {
- id 'com.android.library'
- id 'kotlin-android'
- }
- aptlib-anno
- plugins {
- id 'java-library'
- }
- aptlib-processor
- 是plugins {
- id 'java-library'
- }
這個(gè)要記清楚,很多博主估計(jì)自己都沒有寫過apt,分不清楚AndroidLib和javaLib
apt 本來java 提供的,另外 Android庫中不允許繼承AbstractProcessor
2 、定義注解-自定義注解
記住要在 aptlib-anno 庫下面創(chuàng)建
- @Retention(RetentionPolicy.CLASS)
- @Target(ElementType.FIELD)
- public @interface BindView {
- int value();
- }
定義了運(yùn)行時(shí)注解BindView,其中value()用于獲取對(duì)應(yīng)View的id;
- @Retention(RetentionPolicy.CLASS):表示編譯時(shí)注解
- @Target(ElementType.FIELD):表示注解范圍為類成員(構(gòu)造方法、方法、成員變量)
- @Retention:定義被保留的時(shí)間長短
- RetentionPoicy.SOURCE、RetentionPoicy.CLASS、RetentionPoicy.RUNTIME
- @Target:定義所修飾的對(duì)象范圍
- TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE等
3、定義注解處理器-動(dòng)態(tài)生成關(guān)聯(lián)文件
aptlib-processor 庫
首先在本lib下添加依賴
- dependencies {
- implementation 'com.google.auto.service:auto-service:1.0-rc2'
- implementation project(':aptlib-anno')
- }
創(chuàng)建BindViewProcessor
- @AutoService(Processor.class)
- 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 roundEnv) {
- 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);
- try {
- mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName());
- JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
- Writer writer = jfo.openWriter();
- writer.write(proxyInfo.generateJavaCode());
- writer.flush();
- writer.close();
- } catch (IOException e) {
- mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + "error");
- }
- }
- mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
- return true;
- }
- }
- 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.gavin.apt_library.*;\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;
- }
- }
- init:初始化??梢缘玫絇rocessingEnviroment,ProcessingEnviroment提供很多有用的工具類Elements, Types 和 Filer
- getSupportedAnnotationTypes:指定這個(gè)注解處理器是注冊(cè)給哪個(gè)注解的,這里說明是注解BindView
- getSupportedSourceVersion:指定使用的Java版本,通常這里返回SourceVersion.latestSupported()
- process:可以在這里寫掃描、評(píng)估和處理注解的代碼,生成Java文件
- auto-service 庫:自動(dòng)生成代碼需要借助的庫
4、寫工具類BindViewTools
在aptlib項(xiàng)目中寫綁定類
- 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 (Exception e) {
- e.printStackTrace();
- }
- }
- }
5、主項(xiàng)目app中引入
- implementation project(path: ':aptlib')
- annotationProcessor project(path: ':aptlib-process')
在MainActivity中,在View的前面加上BindView注解,把id傳入即可
- public class MainActivity extends AppCompatActivity {
- @BindView(R.id.tv)
- TextView mTextView;
- @BindView(R.id.btn)
- Button mButton;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- BindViewTools.bind(this);
- mTextView.setText("bind TextView success");
- mButton.setText("bind Button success");
- }
- }
總結(jié)
1、APT技術(shù)其實(shí)就是自定義注解和注解處理器,在編譯期間生成Java文件,類似于IOC控制反轉(zhuǎn),可以方便的進(jìn)行解耦;
2、如果你也可以實(shí)現(xiàn)很多不同的項(xiàng)目,比如路由框架等等,后續(xù)也會(huì)寫一些apt的項(xiàng)目