本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。
一、APT是什么?有什么用,带着疑惑来学习
- APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出;
- 简单来说就是在编译期,通过注解生成.java文件;
- 使用APT的优点就是方便、简单,可以少些很多重复的代码;用过ButterKnife、Dagger、EventBus等注解框架的同学就能感受到,利用这些框架可以少些很多代码,只要写一些注解就可以了,他们不过是通过注解,帮助生成了一些高效代码;
二、APT应用-仿照ButterKnife写个注解
通过APT实现一个功能,通过对View变量的注解,实现View的绑定
1、创建几个Library来声明
- Android Library:aptlibs 正常的写Android的lib
- Java or Kotlin Library:aptlib-anno (专门放我们编写的注解)
- Java or Kotlin Library :aptlib-processor (编写动态生成文件的逻辑)
- aptlibs
- plugins {
- id 'com.android.library'
- id 'kotlin-android'
- }
- aptlib-anno
- plugins {
- id 'java-library'
- }
- aptlib-processor
- 是plugins {
- id 'java-library'
- }
这个要记清楚,很多博主估计自己都没有写过apt,分不清楚AndroidLib和javaLib
apt 本来java 提供的,另外 Android库中不允许继承AbstractProcessor
2 、定义注解-自定义注解
记住要在 aptlib-anno 库下面创建
- @Retention(RetentionPolicy.CLASS)
- @Target(ElementType.FIELD)
- public @interface BindView {
- int value();
- }
定义了运行时注解BindView,其中value()用于获取对应View的id;
- @Retention(RetentionPolicy.CLASS):表示编译时注解
- @Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)
- @Retention:定义被保留的时间长短
- RetentionPoicy.SOURCE、RetentionPoicy.CLASS、RetentionPoicy.RUNTIME
- @Target:定义所修饰的对象范围
- TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE等
3、定义注解处理器-动态生成关联文件
aptlib-processor 库
首先在本lib下添加依赖
- dependencies {
- implementation 'com.google.auto.service:auto-service:1.0-rc2'
- implementation project(':aptlib-anno')
- }
创建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,创建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);
- }
- /**
- * 创建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:初始化。可以得到ProcessingEnviroment,ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
- getSupportedAnnotationTypes:指定这个注解处理器是注册给哪个注解的,这里说明是注解BindView
- getSupportedSourceVersion:指定使用的Java版本,通常这里返回SourceVersion.latestSupported()
- process:可以在这里写扫描、评估和处理注解的代码,生成Java文件
- auto-service 库:自动生成代码需要借助的库
4、写工具类BindViewTools
在aptlib项目中写绑定类
- 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、主项目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");
- }
- }
总结
1、APT技术其实就是自定义注解和注解处理器,在编译期间生成Java文件,类似于IOC控制反转,可以方便的进行解耦;
2、如果你也可以实现很多不同的项目,比如路由框架等等,后续也会写一些apt的项目