ButterKnife核心技术揭秘

ButterKnife核心技术揭秘

ButterKnife是来自Android届大牛JakeWharton的项目,这里我就再也不赘述了,想必你们都有了解过,这里我主要给你们介绍这个项目的核心技术点,以及基于这个技术点,我给你们展现一下相似于ButterKnife的Demo。废话很少说 项目地址java

PS:CSDN对mark down的支持有bug,你们能够看wiki.android

项目原理:

是一个基于注解的项目,可是他与普通的注解方式不同,普通注解:代码执行的过程当中解析相关注解,按照注解的意义作相应的处理;该项目的方式是采用了:annotation.processing.Processor技术,在编译阶段apt介入,将注解部分生成相应代码,执行的时候是执行相关代码,不须要再进行注解解析。git

优点:

本质上讲和手动编写那些代码没有什么本质区别,可是这样的意义在于代码是自动生成的!咱们也不须要关心这部分代码,这部分代码也不须要咱们去维护,下降了咱们平常的一些代码,维护的代码下降,再也不去作findViewById或者setOnClickListener这样的重复工做;另外,该项目采用的annotation.processing.Processor技术很大程度上避免了使用注解的时候须要在运行时解析注解代理的性能消耗。因此学习该项目应该更关注annotation.processing.Processor技术,它是一个很好的技术点。github

建立一个相似于ButterKnife的项目

有须要的看下,可滤过直接看技术讲解,这里注意是我的在实践的时候发现了一些坑,特地在这里Mark一下。
  • 建立一个普通的Android项目AnnotationProcessorDemo
  • 建立一个Module类型是Android Library:AnnotationBind
  • 建立一个专门annotation的Java Library:Annotations
  • 建立一个核心的预编译处理Module,特别要注意这个Module是Java Library:Complie

注意点:web

  • app的Module里边Gradle配置apt plugin
    gradle
    apply plugin: 'com.neenbedankt.android-apt'
    dependencies {
    compile project(':annotations')
    compile project(':annotationbind')
    apt project(':compiler')
    }
  • project的build.gradle配置classpath中添加aptapp

    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
  • Complie这个module须要配置ide

    apply plugin: 'java'
    apply plugin: 'checkstyle'
    dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':annotations')
    // Square
    compile 'com.squareup:javapoet:1.4.0'
    // Google project to get M
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    }

Annotation Processor 技术

这是一个JavaC的工具,也就是Java编译源码到字节码的一个预编译工具,会在代码编译的时候调用到。他有一个抽象类,只需实现抽象类,就能够在预编译的时候被编译器调用,就能够在预编译的时候完成一下你想完成工做。好比代码注入!!svg

public abstract class BaseProcessor extends AbstractProcessor {
    protected Messager messager;
    protected Elements elements;
    protected Filer filer;

     @Override
    public boolean process(Set<? extends TypeElement> annotations,          RoundEnvironment roundEnv) {
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supports = new LinkedHashSet<>();
        supports.add(BindString.class.getCanonicalName());
        supports.add(BindInt.class.getCanonicalName());

        return supports;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        elements = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
    }

    protected void printLog(Diagnostic.Kind kind, String message) {
        messager.printMessage(kind, message);
    }

    protected void printLog(Element element, Diagnostic.Kind kind, String message)   {
        messager.printMessage(kind, message, element);
    }

    protected void printLog(Element element, String message, Object... args) {
        if (args.length > 0) {
            message = String.format(message, args);
        }
        messager.printMessage(ERROR, message, element);
    }
}
  • Set getSupportedAnnotationTypes():制定须要预编译器处理的注解,这里返回的是String的集合,必须是注解类型的合法全称。这里能够理解为过滤器中的过滤条件,制定后,编译器会根据制定的过滤类型扫描代码。
  • SourceVersion getSupportedSourceVersion():Jave的版本控制,建议使用SourceVersion.latestSupported()。
  • void init(ProcessingEnvironment processingEnv):初始化处理入口,是编译器调用的方法,这里提供了ProcessingEnvironment引用,这个但是个好东西,我是用再作一个基类的形式封装基于ProcessingEnvironment的操做,咱们须要经过它得到Messager(log打印的类),Elements(注解扫描处理),以及Filer(代码注入类)。
  • boolean process(Set

注册你的注解处理器

Android开发者会在这时候疑惑,我实现了AbstractProcessor就能够了吗?好比增长一个Activity,我继承了Activity基类是不够的,还得在AndroidManifest.xml文件中注册Activity,程序才能调用这个Activity或者说找到这个程序的Activity入口。
这里Google提供了一个解决方案,只需使用 com.google.auto.service:auto-service这个项目,在继承AbstractProcessor类添加注解@AutoService(Processor.class),这个解决方案就会自动帮咱们完成这个注解处理器的注册。工具

注解的扫描结果的处理

处理器工做的原理:1.扫描代码之后调用process方法返回给处理入口;2.处理入口须要根据注册的注解查询使用了该注解的类,以及注解中获取配置的值;3.生成代码注入。性能

AutoService(Processor.class)
public class BindingProcessor extends BaseProcessor {
    private static final String BIND_CLASS_SUFFIX = "$$AnnotationBinder";

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        printLog(Diagnostic.Kind.OTHER, "BindingProcessor process.... ");

            // 处理注解类
        Map<TypeElement, BindingClass> bindingClassMap = findAndParseTargets(roundEnv);

        for (Map.Entry<TypeElement, BindingClass> entry : bindingClassMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            BindingClass bindingClass = entry.getValue();

            try {
                printLog(Diagnostic.Kind.OTHER, "BindingProcessor code:" + bindingClass.brewJava().toString());
                //处理结果注入代码
                bindingClass.brewJava().writeTo(filer);
            } catch (IOException e) {
                printLog(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                        e.getMessage());
            }
        }
        return true;
    }

    ```

1.核心的扫描处理方法,使用roundEnv查询,而后逐个处理:
```Java
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment roundEnv) {
        ...
        //其中:根据注解类得到使用了这个注解的类列表。
        Set<? extends Element> bindStringSet = roundEnv.getElementsAnnotatedWith(BindString.class);
        ...
    }




<div class="se-preview-section-delimiter"></div>

2.而后处理这个注解咱们要作的操做,记录使用了BindString.class这个注解的一个类,解析注解的配置或者值,保存注解信息,和所在的类的信息:

private void parseResourceString(Element element, Map<TypeElement, BindingClass> targetClassMap,Set<String> erasedTargetNames) {
            ...
        // 使用了这个注解的类的信息
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
        ...
        // 得到注解里边的值或者说配置
        String value = element.getAnnotation(BindString.class).value();
        String name = element.getSimpleName().toString();
        ...
    }




<div class="se-preview-section-delimiter"></div>

3.代码注入:

for (Map.Entry<TypeElement, BindingClass> entry : bindingClassMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            BindingClass bindingClass = entry.getValue();

            try {
                printLog(Diagnostic.Kind.OTHER, "BindingProcessor code:" + bindingClass.brewJava().toString());
                //处理结果注入代码
                bindingClass.brewJava().writeTo(filer);
            } catch (IOException e) {
                printLog(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                        e.getMessage());
            }
        }




<div class="se-preview-section-delimiter"></div>

Java代码的生成

代码的生成能够是刀耕火种式的,字符串拼接,也能够采用square公司提供的工具com.squareup:javapoet,里边封装了Java生成工具,很方便,能够灵活配置。这里我就介绍一下类的生成和方法的生成:

//生成完整类
JavaFile brewJava() {
        TypeSpec.Builder result = TypeSpec.classBuilder(className)
                .addModifiers(PUBLIC)
                .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));

        if (parentViewBinder != null) {
            result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
                    TypeVariableName.get("T")));
        } else {
            result.addSuperinterface(ParameterizedTypeName.get(BINDER, TypeVariableName.get("T")));
        }

        result.addMethod(createBindMethod());

        return JavaFile.builder(classPackage, result.build())
                .addFileComment("This is a class, create by annotation processor!")
                .build();
    }

    //生成方法
    private MethodSpec createBindMethod() {
        MethodSpec.Builder result = MethodSpec.methodBuilder("bind")
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(TypeVariableName.get("T"), "target", FINAL)
                .addParameter(Object.class, "source");

        // Emit a call to the superclass binder, if any.
        if (parentViewBinder != null) {
            result.addStatement("super.bind(target, source)");
        }

        for (StringBinding binding : stringBindings) {
            result.addStatement("target.$L = \"$L\"", binding.getName(), binding.getValue());
        }

        for (IntBinding binding : intBindings) {
            result.addStatement("target.$L = $L", binding.getName(), binding.getValue());
        }

        return result.build();
    }




<div class="se-preview-section-delimiter"></div>

最后的结果是JavaFile,而后用上边提到的Filer工具去完成代码注入。

出错处理

其实在基类里边已经给你们提到了Messager,在log打印的时候必须输入级别,若是是Error的话预编译器会报错,预编译会中断,编译失败!

messager.printMessage(ERROR, message, element);
  ```
也会提供别的级别的日志:




<div class="se-preview-section-delimiter"></div>

```Java
enum Kind {
        /** * Problem which prevents the tool's normal completion. */
        ERROR,
        /** * Problem which does not usually prevent the tool from * completing normally. */
        WARNING,
        /** * Problem similar to a warning, but is mandated by the tool's * specification. For example, the Java&trade; Language * Specification mandates warnings on certain * unchecked operations and the use of deprecated methods. */
        MANDATORY_WARNING,
        /** * Informative message from the tool. */
        NOTE,
        /** * Diagnostic which does not fit within the other kinds. */
        OTHER,
    }
enum Kind {
        /** * Problem which prevents the tool's normal completion. */
        ERROR,
        /** * Problem which does not usually prevent the tool from * completing normally. */
        WARNING,
        /** * Problem similar to a warning, but is mandated by the tool's * specification. For example, the Java&trade; Language * Specification mandates warnings on certain * unchecked operations and the use of deprecated methods. */
        MANDATORY_WARNING,
        /** * Informative message from the tool. */
        NOTE,
        /** * Diagnostic which does not fit within the other kinds. */
        OTHER,
    }