前言

Android开发中,APT(Annotation Processing Tool)是一种强壮的东西,它可以让开发者在编译期间处理注解,生成额定的代码。经过APT,咱们可以完成很多高级功用,比方主动生成代码、完成依靠注入、生成路由表等。本文将深化探讨APT的运用以及背面的原理。

APT的基本原理

APT的基本原理是在编译期间扫描和处理源代码中的注解,然后依据注解生成相应的Java代码。这些生成的代码可以在编译后被编译器包括到终究的APK中。

APT的运作流程

  1. 扫描注解: 首要,编译器会扫描源代码中的注解,找到被符号的元素。
  2. 解析注解: 然后,APT会解析这些注解,获取注解中界说的信息。
  3. 生成代码: 接着,依据注解中的信息,APT会生成相应的Java代码。
  4. 编译代码: 最后,生成的Java代码会被编译器编译成.class文件,与其他源代码一同构建成APK。

APT的运用场景

APT在Android开发中有着广泛的运用,其中一些典型的运用场景包括:

  1. 主动生成代码: 经过APT,咱们可以在编译期间生成一些重复性的代码,比方Parcelable完成、ViewHolder的生成与添加日志等,然后削减手动编写重复代码的作业量。
  2. 依靠注入: APT可以结合注解处理器,完成依靠注入的功用。经过在注解中指定依靠的对象,注解处理器可以在编译期间生成依靠注入的代码,然后完成依靠注入的功用。
  3. 路由办理: APT可以用来生成路由表,然后完成页面跳转的办理。经过在注解中指定页面的路径和参数,APT可以在编译期间生成路由表,然后完成页面跳转的主动化办理。

优势

APT 具有以下优势:

  1. 进步开发功率: APT 可以主动生成代码,削减开发人员的手动编码作业。
  2. 代码愈加简洁高雅: 经过 APT 生成的代码,通常愈加简洁高雅,易于了解和保护。
  3. 进步代码质量: APT 可以协助开发人员防止一些常见的过错,进步代码质量。

运用代码示例

下面经过一个简单的例子来演示APT的运用,完成一个简单的findViewByIdsetText的功用。

界说注解

首要界说一个注解@BindView

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    @IdRes int[] value();
}

在这里运用了@Target@Retention两个重要的元注解,用来对注解进行界说和润饰。

@Target

@Target注解用于指定注解可以运用的范围,即注解可以放置在哪些元素之上。它有一个参数,类型为ElementType[],表明注解可以运用的目标元素类型。

常见的ElementType包括:

  • ElementType.TYPE: 类、接口、枚举
  • ElementType.FIELD: 字段
  • ElementType.METHOD: 办法
  • ElementType.PARAMETER: 参数
  • ElementType.CONSTRUCTOR: 结构办法
  • ElementType.LOCAL_VARIABLE: 局部变量
  • ElementType.ANNOTATION_TYPE: 注解类型
  • ElementType.PACKAGE: 包

例如,当咱们指定@Target(ElementType.FIELD)时,表明该注解只能运用在字段上,不能运用在其他元素上。

@Retention

@Retention注解用于指定注解的生命周期,即注解在编译后是否保存到运行时。它有一个参数,类型为RetentionPolicy,表明注解的保存战略。

常见的保存战略包括:

  • RetentionPolicy.SOURCE: 注解仅保存在源代码中,编译时会被丢掉,不会包括在生成的class文件中。
  • RetentionPolicy.CLASS: 注解保存在编译后的class文件中,但在运行时会被疏忽,默认值。在Kotlin中对应的是BINARY
  • RetentionPolicy.RUNTIME: 注解保存在编译后的class文件中,并且在运行时可以经过反射获取到。

通常情况下,咱们期望自界说注解在运行时保存,以便在运行时经过反射来获取注解信息,因而,一般会指定@Retention(RetentionPolicy.RUNTIME)

例如,当咱们指定@Retention(RetentionPolicy.RUNTIME)时,表明该注解在编译后的class文件中保存,并且可以在运行时经过反射获取到。

编写注解器

public class Processor extends AbstractProcessor {
    private Filer mFiler;
    private Messager mMessager;
    private Elements mElementUtils;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
        mElementUtils = processingEnv.getElementUtils();
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            //获取与annotation相匹配的TypeElement,即有注释声明的class
            Set<TypeElement> elements = getTypeElementsByAnnotationType(annotations, roundEnv.getRootElements());
            for (TypeElement typeElement : elements) {
                //包名
                String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
                //类名
                String typeName = typeElement.getSimpleName().toString();
                //全称类名
                ClassName className = ClassName.get(packageName, typeName);
                //主动生成类全称名
                ClassName autoGenerationClassName = ClassName.get(packageName,
                        NameUtils.getAutoGeneratorTypeName(typeName));
                //构建主动生成的类
                TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(autoGenerationClassName)
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(Keep.class);
                //添加结构办法
                typeBuilder.addMethod(MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY)
                        .addStatement("$N($N)",
                                NameUtils.Method.BIND_VIEW,
                                NameUtils.Variable.ANDROID_ACTIVITY)
                        .build());
                //添加bindView成员办法
                MethodSpec.Builder bindViewBuilder = MethodSpec.methodBuilder(NameUtils.Method.BIND_VIEW)
                        .addModifiers(Modifier.PRIVATE)
                        .returns(TypeName.VOID)
                        .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY);
                //添加办法内容
                for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
                    BindView bindView = variableElement.getAnnotation(BindView.class);
                    if (bindView != null) {
                        bindViewBuilder.addStatement("$N.$N=($T)$N.findViewById($L)",
                                NameUtils.Variable.ANDROID_ACTIVITY,
                                variableElement.getSimpleName(),
                                variableElement,
                                NameUtils.Variable.ANDROID_ACTIVITY,
                                bindView.value()[0]
                        ).addStatement("$N.$N.setText($N.getString($L))",
                                NameUtils.Variable.ANDROID_ACTIVITY,
                                variableElement.getSimpleName(),
                                NameUtils.Variable.ANDROID_ACTIVITY,
                                bindView.value()[1]);
                    }
                }
                typeBuilder.addMethod(bindViewBuilder.build());
                //写入java文件
                try {
                    JavaFile.builder(packageName, typeBuilder.build()).build().writeTo(mFiler);
                } catch (IOException e) {
                    mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement);
                }
            }
        }
        return true;
    }
    private Set<TypeElement> getTypeElementsByAnnotationType(Set<? extends TypeElement> annotations, Set<? extends Element> elements) {
        ....
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return new TreeSet<>(Arrays.asList(
                BindView.class.getCanonicalName(),
                Keep.class.getCanonicalName())
        );
    }
}

在这个处理器中,按照一个类的创建次序做了以下几步:

  1. 生成构建的类
  2. 添加类的结构办法,并在结构办法中引用咱们需求的bindView办法
  3. 为类添加bindView成员办法
  4. bindView办法中添加完成代码,也便是findVieByIdsetText的代码完成
  5. 经过javaPoet写入到java文件中

JavaPoet是一个用于生成Java代码的库,它供给了一套API来构建Java源代码,并且可以输出成Java文件。

经过上面的步骤,机会主动帮咱们生成一个绑定View的代码类。

注解运用

接下来,咱们来演示如何运用@BindView注解:

class MainActivity : AppCompatActivity() {
    @BindView(R.id.public_service, R.string.public_service)
    lateinit var sName: TextView
    @BindView(R.id.personal_wx, R.string.personal_wx)
    lateinit var sPhone: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Butterknife.bind(this)
    }
}

MainActivity中,咱们运用了@BindView注解来符号TextView字段,然后在onCreate办法中调用Butterknife.bind(this)办法,即可主动为textView字段进行赋值,无需手动调用findViewById办法。

Butterknife是一个自界说的类,内部供给bind办法,经过反射来构建上面咱们主动生成的绑定类的实例。

主动生成的类

最后,再来看下主动生成的类的真实面目。

@Keep
public class MainActivity$Binding {
  public MainActivity$Binding(MainActivity activity) {
    bindView(activity);
  }
  private void bindView(MainActivity activity) {
    activity.sName=(TextView)activity.findViewById(2131165265);
    activity.sName.setText(activity.getString(2131427362));
    activity.sPhone=(TextView)activity.findViewById(2131165262);
    activity.sPhone.setText(activity.getString(2131427360));
  }
}

注意事项与优化技巧

在运用APT时,有一些注意事项和优化技巧需求咱们注意:

  • 防止乱用APT: 尽管APT可以协助咱们完成很多高级功用,但是乱用APT会导致编译时刻过长,添加项目的复杂度。因而,在运用APT时,需求权衡利弊,防止过度运用。
  • 优化代码生成: 在编写注解处理器时,需求尽量优化生成的代码,削减生成的代码量,进步代码的履行功率。
  • 处理异常情况: 在处理注解时,需求考虑到各种异常情况,比方注解不存在、注解参数过错等情况,然后进步代码的健壮性。

结语

经过本文的介绍,信任我们已经对APT有了更深化的了解,并且可以在实际的项目中运用APT来进步开发功率。APT作为一种强壮的东西,在Android开发中有着广泛的运用前景,期望我们可以善加利用,发挥其最大的效果。

推荐

android_startup: 供给一种在运用启动时可以愈加简单、高效的方式来初始化组件,优化启动速度。不只支持Jetpack App Startup的全部功用,还供给额定的同步与异步等待、线程操控与多进程支持等功用。

AwesomeGithub: 根据Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。运用Kotlin语言进行开发,项目架构是根据JetPack&DataBinding的MVVM;项目中运用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等盛行开源技能。

flutter_github: 根据Flutter的跨平台版别Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 协助读者可以更快的把握与了解所阐述的关键。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一同共勉。