[Android] ButterKnifeProcessor 工作流程分析

前言

[Android] ButterKnife 浅析 中,我们了解了 ButterKnife 的用法,比较简单。

本次文章我们来学习一下 ButterKnife 的 ButterKnifeProcessor 注解处理器,注解处理器能够解析代码中的注解信息,生成相应的 Java 类,这也是 ButterKnife 的关键实现原理。

建议在阅读前先了解下 Java 中『注解』的概念。

准备内容

APT

APT(Annotation processing tool)是在编译时,扫描和处理注解的一个构建工具,可以在编译源代码时额外生成 Java 源代码。

AbstractProcessor

AbstractProcessor 是扫描和处理注解的关键类,ButterKnife 自定义的 Processor 就需要继承自该类。

代码示例如下:

public class MyProcessor extends AbstractProcessor {
    // 在 Processor 创建时调用并执行的初始化操作
    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    // 关键方法,进行扫描和处理注解,并生成新的源代码
    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

    // 指定需要注册的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() { }

    // 指定支持的 Java 版本
    @Override
    public SourceVersion getSupportedSourceVersion() { }

}

添加依赖

还记得我们使用 ButterKnife 前所做的添加吗?

dependencies {
  classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

dependencies {
  compile 'com.jakewharton:butterknife:8.0.1'
  apt 'com.jakewharton:butterknife-compiler:8.0.1'
}

这里 com.jakewharton:butterknife-compiler 就是自定义的注解处理器,我们在 Gradle 中注册使用它。

然而我在项目结构中找了很久也没有找到这个库的文件,有可能是在编译时才去访问的,如果需要可以在 GitHub 中找到:

butterknife-compiler

ButterKnifeProcessor 工作流程

上面注册完自定义的注解处理器后,我们就知道,在编译源代码时,APT 会调用 Processor 来查找解析注解。

主要逻辑

这里主要看 process 处理方法的内容。

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {

    // 查找并解析注解
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

    // 去除注解的键值
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

    // 生成源代码
      try {
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }
  return true;
}

可以看到 process 方法的逻辑还是比较容易理解的,就是获取到注解的键值并生成源代码。

查找和解析注解

首先看 findAndParseTargets(RoundEnvironment env) 方法,通过参数 RoundEnvironment env 可以找到我们想要的某一个被注解的元素。

该方法会查找所有 ButterKnife 的注解来进行解析,我们选择最简单的 @BindInt 来看一下:

private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    // Process each @BindInt element.
    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceInt(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindInt.class, e);
      }
    }
    ...

这里调用了 parseResourceInt 方法传入了被注解的元素进行解析:

  private void parseResourceInt(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<TypeElement> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target type is int.
    if (element.asType().getKind() != TypeKind.INT) {
      error(element, "@%s field type must be 'int'. (%s.%s)", BindInt.class.getSimpleName(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

    // Verify common generated code restrictions.
    hasError |= isInaccessibleViaGeneratedCode(BindInt.class, "fields", element);
    hasError |= isBindingInWrongPackage(BindInt.class, element);

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    String name = element.getSimpleName().toString();
    int id = element.getAnnotation(BindInt.class).value();

    BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    FieldResourceBinding binding = new FieldResourceBinding(id, name, "getInteger", false);
    bindingClass.addResource(binding);

    erasedTargetNames.add(enclosingElement);
  }

方法分为前后两部分,先进行了一些检验,检验通过则调用 getOrCreateTargetClass 获取或生成一个类放入数组中。

isInaccessibleViaGeneratedCode 方法检验了:

  1. 方法修饰符不能为 private 和 static
  2. 包类型不能为非 Class
  3. 类的修饰符不能为 private

isBindingInWrongPackage 则检验了包名,不能以 android 或 java 开头。

生成类文件

解析完每个被注解的元素之后会得到一个 Map

参考资料

AndroidSutdio 编译时自动生成源代码

Android 浅析 ButterKnife (二) 源码解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 录屏流程主要包括以下几个步骤: 1. 创建 MediaProjection 对象:用户启动录屏功能后,系统会创建一个 MediaProjection 对象,用于捕获屏幕内容。 2. 创建 MediaCodec 编码器:系统会创建一个 MediaCodec 对象,用于将屏幕内容编码成视频数据流。 3. 创建 Surface 对象:系统会创建一个 Surface 对象,用于将屏幕内容传递给 MediaCodec 编码器。 4. 开始录屏:系统会将屏幕内容通过 MediaProjection 对象输出到 Surface 对象中,并将视频数据传递给 MediaCodec 编码器进行编码。 5. 停止录屏:用户停止录屏后,系统会停止将屏幕内容输出到 Surface 对象中,并将编码器中的数据输出到文件或者网络中。 6. 释放资源:录屏结束后,系统会释放 MediaProjection、MediaCodec 和 Surface 等资源。 具体流程如下: 1. 用户启动录屏功能,系统会弹出一个对话框询问用户是否允许录屏。 2. 如果用户同意,系统会创建一个 MediaProjection 对象,并将屏幕内容输出到该对象中。 3. 系统会创建一个 MediaCodec 编码器,并设置编码参数。 4. 系统会创建一个 Surface 对象,并将其与编码器关联。 5. 系统会通过 Surface 对象将屏幕内容传递给编码器进行编码。 6. 当录屏结束后,系统会停止将屏幕内容输出到 MediaProjection 对象中,并将编码器中的数据输出到文件或者网络中。 7. 录屏结束后,系统会释放 MediaProjection、MediaCodec 和 Surface 等资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值