一 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