Butterknife - 使用和源码解析(更新中......)

一 butterknife的引言和基本使用

第一步:
compile 'com.jakewharton:butterknife:8.5.1'

第二步:初始化布局之后调用
ButterKnife.bind(this);

第三步:
@BindView(R.id.content_tv) TextView contentTv;
@BindView(R.id.head_img) ImageView headImg;
//点击事件
@OnClick(R.id.submit)
public void buttonClick(Button button){
   //TODO ...
}

//listview item点击事件
@OnItemClick(R.id.listview)
  public void itemClick(ListView listView){
   //TODO ...
}

//多个控件具有相同的点击事件
@OnClick({ R.id.btn1, R.id.btn2, R.id.btn3 })
public void buttonsClick(Button button){
  //TODO ...
}

问题:(之前注解框架)反射?RUNTIME所做的。对性能有影响。
解决:APT。编译时解析技术。

Butterknife虽然采用注解进行注入,这个注解不是RUNTIME,是编译时生成的代码,所以这对运行时
没有任何副作用。当然了这对编译器有一点点的时间成本。

有三个知识点需要重温一下:注解、反射、Java的注解处理器。

二、Butterknife原理必备知识点1:注解

@Deprecated
@Target(ElementType.TYPE)  //  用来描述类或者接口
// 描述注解生命周期:(RUNTIME 只是在我们的运行时有效)
@Retention(RetentionPolicy.RUNTIME)   
@Inherited    // 可继承的
public @interface metaTest {  // @interface 标明 metaTest 是一个自定义注解
    public String doTest();
}
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    /* 字段声明(包括枚举常量)*/
    FIELD,

    /** Method declaration 方法声明 */
    METHOD,

    /** Formal parameter declaration 描述参数 */
    PARAMETER,

    /** Constructor declaration 构造函数声明 */
    CONSTRUCTOR,

    /** Local variable declaration 局部变量声明 */
    LOCAL_VARIABLE,

    /** Annotation type declaration 注释类型声明 */
    ANNOTATION_TYPE,

    /** Package declaration 描述包 */
    PACKAGE,

    /**
     * Type parameter declaration
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     */
    TYPE_USE
}
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     *
     * 将被编译器所丢弃。源文件会保留。
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     *
     * 在 class 文件中使用,需要注意的是,有可能被JVM所抛弃。
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     *
     * 就在我们的运行时有效。
     */
    RUNTIME
}
butterknife注解库:
@Retention(CLASS) //在class文件是保留的,在runtime是不存在的。
@Target(FIELD)    // 表明这个注解,是用来修饰我们预变量的。
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes   
  int value();
}

@IdRes:
自定义注解,在安卓给我们提供的支持的注解包中自定义的,它可以通过自定义注解来完成对变量的注解,还可以通过自定义注解来完成对注解的注解,还有想给大家提醒的一点是,刚才说的第一个@Retention这个注解,它传入的是CLASS,这就意味着Butterknife,是在编译时生成绑定代码,因为这里不是RUNTIME。

当然了,它不会影响我们运行时的速度,但会影响我们编译时的速度。

三 butterknife原理必备知识点2:APT工作原理

APT:注解处理器(Annotation Processor)是java的一个工具。

利用这个注解处理器,我们可以在编译时扫描和处理所需要的注解,这个注解包括你的自定义注解,
同时你最后注册相应的注解处理器。

什么是注解处理器?

每一个处理器都是继承于AbstractProcessor。

package javax.annotation.processing;

import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Objects;
import javax.lang.model.element.*;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic;

public abstract class AbstractProcessor implements Processor {
    /**
     * Processing environment providing by the tool framework.
     */
    protected ProcessingEnvironment processingEnv;
    private boolean initialized = false;

    /**
     * Constructor for subclasses to call.
     */
    protected AbstractProcessor() {}

    /**
     * If the processor class is annotated with {@link
     * SupportedOptions}, return an unmodifiable set with the same set
     * of strings as the annotation.  If the class is not so
     * annotated, an empty set is returned.
     *
     * @return the options recognized by this processor, or an empty
     * set if none
     */
    public Set<String> getSupportedOptions() {
        SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
        if  (so == null)
            return Collections.emptySet();
        else
            return arrayToSet(so.value());
    }

    /**
     * If the processor class is annotated with {@link
     * SupportedAnnotationTypes}, return an unmodifiable set with the
     * same set of strings as the annotation.  If the class is not so
     * annotated, an empty set is returned.
     *
     * @return the names of the annotation types supported by this
     * processor, or an empty set if none
     */
    public Set<String> getSupportedAnnotationTypes() {
            SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
            if  (sat == null) {
                if (isInitialized())
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                             "No SupportedAnnotationTypes annotation " +
                                                             "found on " + this.getClass().getName() +
                                                             ", returning an empty set.");
                return Collections.emptySet();
            }
            else
                return arrayToSet(sat.value());
        }

    /**
     * If the processor class is annotated with {@link
     * SupportedSourceVersion}, return the source version in the
     * annotation.  If the class is not so annotated, {@link
     * SourceVersion#RELEASE_6} is returned.
     *
     * @return the latest source version supported by this processor
     */
    public SourceVersion getSupportedSourceVersion() {
        SupportedSourceVersion ssv = 
               this.getClass().getAnnotation(SupportedSourceVersion.class);
        SourceVersion sv = null;
        if (ssv == null) {
            sv = SourceVersion.RELEASE_6;
            if (isInitialized())
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                           "No SupportedSourceVersion annotation " +
                            "found on " + this.getClass().getName() +
                            ", returning " + sv + ".");
        } else
            sv = ssv.value();
        return sv;
    }


    /**
     * Initializes the processor with the processing environment by
     * setting the {@code processingEnv} field to the value of the
     * {@code processingEnv} argument.  An {@code
     * IllegalStateException} will be thrown if this method is called
     * more than once on the same object.
     *
     * @param processingEnv environment to access facilities the tool framework
     * provides to the processor
     * @throws IllegalStateException if this method is called more than once.
     */
    public synchronized void init(ProcessingEnvironment processingEnv) {
        if (initialized)
            throw new IllegalStateException("Cannot call init more than once.");
        Objects.requireNonNull(processingEnv, 
              "Tool provided null ProcessingEnvironment");

        this.processingEnv = processingEnv;
        initialized = true;
    }

    /**
     * {@inheritDoc}
     */
    public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);

    /**
     * Returns an empty iterable of completions.
     *
     * @param element {@inheritDoc}
     * @param annotation {@inheritDoc}
     * @param member {@inheritDoc}
     * @param userText {@inheritDoc}
     */
    public Iterable<? extends Completion> getCompletions(Element element,
                                                         AnnotationMirror annotation,
                                                         ExecutableElement member,
                                                         String userText) {
        return Collections.emptyList();
    }

    /**
     * Returns {@code true} if this object has been {@linkplain #init
     * initialized}, {@code false} otherwise.
     *
     * @return {@code true} if this object has been initialized,
     * {@code false} otherwise.
     */
    protected synchronized boolean isInitialized() {
        return initialized;
    }

    private static Set<String> arrayToSet(String[] array) {
        assert array != null;
        Set<String> set = new HashSet<String>(array.length);
        for (String s : array)
            set.add(s);
        return Collections.unmodifiableSet(set);
    }
}
AbstractProcessor它是一个抽象类,说明我们在使用的时候去继承它,然后去实现它里面的某些方法。
同时它实现了Java里面的一个Processor这个接口。
AbstractProcessor它的构造函数是空的,它的初始化方法都是init这个方法里。

这个init方法是个同步方法,它这个方法需要传入一个ProcessingEnvironment参数,我们来看下这个参数的一些方法:
public interface ProcessingEnvironment {
    Map<String,String> getOptions();
    Messager getMessager();
    Filer getFiler();
    Elements getElementUtils();
    Types getTypeUtils();
    SourceVersion getSourceVersion(); 
    Locale getLocale();
}

Elements getElementUtils():
用来处理Element的工具类;在注解处理过程中,我们扫描所有的java源文件,我们可以把这个源文件,想象成Element的全部,而我们源代码中每个独立的部分,我们就可以把它认作为特定类型的Element。所以我们可以认为Element代表程序当中的元素。而Elements这个工具类,就是用来处理Element所用的。

Types getTypeUtils():Types主要获取源代码中类型的信息。ElementType源代码中的类型。
Filer getFiler():  Filer用于创建文件。
AbstractProcessor里面有一个最核心的方法:
public abstract boolean process(Set<? extends TypeElement> annotations,
           RoundEnvironment roundEnv);
process的重要程度,可以代表我们的main函数,也就代表方法的入口。

在这个方法中你可以去完成扫描、评估、处理注解,等等。所以这里定义成一个抽象类。
process最后生成我们所需要的java代码,通过我们输入的RoundEnvironment这个参数,可以让你查询到包含特定的注解元素。

另外一个重要的方法getSupportedAnnotationTypes():
返回我们的所支持的注解类型,它内部是通过一个集合来判断的。

getSupportedSourceVersion():用来指定你所使用的java版本。

注意:整个的注解处理器,它是运行在自己的java虚拟机当中的。然后我们通过javac来启动一个完整的java虚拟机,
来运行这个处理器。主要是这样的一个过程。

06:20 有一个流程图

APT整个流程:
1)声明的注解的生命周期为CLASS。
2)继承AbstractProcessor类。
3)再调用AbstractProcessor的process方法[最核心]。
   对注解进行处理,在这个方法中就可以对注解进行处理,那么我们在处理的时候,动态生成绑定事件和控件的JAVA代码,是在这里完成的,然后在运行时直接调用,然后在butterknife完成绑定。

四 butterknife原理必备知识点3:反射+运行时注解举例

【反射】
1) 判断任意一个对象所属的类。
2) 构造任意一个类的对象。
3) 判断任意一个所具有的成员变量和方法(通过反射甚至可以调用private方法)。
4) 调用任意一个对象的方法。

【缺点】
反射影响性能。
反射会造成大量的零时对象,大量的零时对象就会造成大量的GC。GC又会造成UI卡顿。

// 表明 TestInterface 用来描述我们的域的,
@Target(ElementType.FIELD)
// 在运行时去获取类的信息,这也是注解和反射相结合的一个例子。
@Retention(RetentionPolicy.RUNTIME)  
public @interface TestInterface {  
    int value() default 100;
}

【问题】接下来我们看下如何通过反射来获取运行时的TestInterface这个注解 ?

public class ReflectMain {
    
    @TestInterface(12)
    public int age;

    public static void main(String[] args) {
        ReflectMain main = new ReflectMain();
        TestInterface testInterface = null;
        try {
            Class clazz = main.getClass();
            // 先通过反射来获取field变量
            Field field = clazz.getField("age");
            // 获得注解的方法
            testInterface = field.getAnnotation(TestInterface.class);
            // 获取注解的值
            System.out.print(testInterface.value());  
        } catch (NoSuchFieldException e) {
            System.out.print("no such field");
        }
    }
}
————————
12
Process finished with exit code 0

五 原理分析:注解处理器如何处理注解和保存注解、如何生成findviewByID代码

ButterKnife的工作原理
1)编译的时候扫描注解,并做相应的处理,生成java代码,生成java代码是调用 javapoet库生成的。
2)调用ButterKnife.bind(this);方法的时候,将ID与对应的上下文绑定在一起。

转载文章:

https://blog.csdn.net/rusbme/article/details/51416868

https://blog.csdn.net/ta893115871/article/details/52497297
https://blog.csdn.net/ta893115871/article/details/52497396
https://blog.csdn.net/ta893115871/article/details/52497441
https://blog.csdn.net/ta893115871/article/details/52497488
https://blog.csdn.net/ta893115871/article/details/52497495












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值