1、前言
做者
JakeWharton
做用
- 依赖动态注入框架
- 减小findView/setListener 类初始化代码,减小工做量
2、简单使用
一、导入库
implementation 'com.jakewharton:butterknife:8.5.1'
implementation 'com.jakewharton:butterknife-compiler:8.5.1'
二、实例代码:
@BindView(R.id.TextView_1)
TextView TextView1;
@OnClick(R.id.button_1)
void OnClick(View view) {
textView1.setText("");
}
setContentView(R.layout.butterknife_layout);
//必须在setContentView绘制好布局以后调用 不然找不到对应的id对象 产生空指针
ButterKnife.bind(this);
3、技术历史
一、早期注入框架技术
反射机制
在Activity中使用反射机制完成注解库的注入早期
缺陷
在Activity runtime运行时大量加载反射注入框架完成依赖注入,会影响App的运行性能,形成UI卡顿,产生更多的临时对象增长内存损耗
二、现今注入框架技术
APT
编译时解析技术,注解注入框架
区别与旧技术
编译时生成非运行时生成
4、依赖注入框架基础
A、注解
分类
一、@Override :当前的方法的定义必定要覆盖其父类的方法
二、@Deprecated :使用该注解编译器会出现警告信息
三、@SuppressWarnings :忽略编译器提示的警告信息
定义:
用来注解其余注解的注解
一、@
Documented:这个注解应该被Java Document这个工具所记录
二、@
Target:代表注解的使用范围
三、@
Retention:描述注解的生命周期
四、@
Inherited:代表注解能够继承的,这个注解应该被用于class的子类
实例:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface metaTest {
public String getTest();
}
一、@Documented:代表这个注解应该被Java Document工具所记录的
二、@Target:传入ElementType. xxx
- TYPE :表示用来描述类或是接口的
- FIELD : 表示用来描述成员域
- METHOD :表示用来描述方法
- PARAMETER :表示用来描述参数
- CONSTRUCTOR :表示用来描述构造器
- LOCAL_VARIABLE :表示用来描述局部变量
- PACKAGE :表示用来描述包
如下是很是用类型:
ANNOTATION_TYPE,
TYPE_PARAMETER,
TYPE_USE
三、@Retention 描述注解生命周期
SOURCE :注解将被编译器所丢弃,原文件会被保留
CLASS :注解在Class文件中使用,有可能会被JVM本身所抛弃,是编译时生成绑定代码的
RUNTIME:注解只在运行时有效
ps:运行时经过反射获取注解的内容
四、@Inherited:表示该注解能够继承
五、@interface :代表这个metaTest 是一个自定义的注解,以及配合上面4个元注解对其进行解释
@BindView
@Retention( CLASS
) //使用该注解代表会在class文件中保留,在runtime时是不存在的
@Target( FIELD )
//该注解用来修饰 域变量
public @interface BindView {
@IdRes //
ps:经过自定义注解完成对变量的注解、对注解的注解
int value();
}
B、APT工做原理-编译时
注意⚠️:
- APT不是经过运行时经过反射机制处理注解的!!!!!!
- 整个注解处理器是运行在本身的Java虚拟机当中的!
Annotation Processor 注解处理器
Javac 工具;编译时扫描、处理注解;须要注册注解处理器
每个处理器都是
继承于AbstractProcessor 抽象类 (使用的时候须要继承并实现其内部的方法)
abstract AbstractProcessor :内几个比较重要的函数
init方法 :会被注解处理工具调用,传入ProcessingEnvironment 提供不少经常使用的工具类共使用
interface ProcessingEnvironment{
Elements 工具类 ,扫描的全部java原文件的,element 表明程序中的元素也就说java原代码
Types :获取原代码中的类型元素信息, Typs 处理Type Element当中一些所想得到的信息
Filter :建立文件所用
}
abstract process() : 重要级别堪比main 函数,方法的入口,每一个处理器主函数入口!
使用:自定义注解器须要实现其方法
做用: 扫描、评估、处理注解工做,并生成须要的java代码
set<String> getSupportedAnnotationTypes ()
返回所支持的注解的类型
SourceVersion getSupportedSourceVersion()
用来指定所使用的java版本
APT 流程
如何生成 字节码文件?
一、生命 注解的生命周期是 Class
二、继承AbstractProcessor 类 (编译时编译器会扫描须要处理的注解)
三、调用AbstractProcessor
的 Process 方法,
对注解进行处理,动态生成绑定事件和控件的java代码
*.java ———input file———> Parse and Enter ———解析———> Annotation Processing (APT解析工具进行解析,不能加入、删除java方法)
|
|
编译成class文件 —————— 生成java代码
C、反射机制
目标
经过开源的process库生成 java 代码
反射
反射机制容许在运行时发现和使用类的信息
反射的做用
一、判断任意一个对象所属的类
二、构造任意一个类的对象
三、判断任意一个类所具备的成员变量和方法 (经过反射甚至能够调用provate方法)
四、调用任意一个对象的方法
反射的缺陷
一、JVM没法对反射部分的代码进行优化,形成性能的损失
二、反射会形成大量的
临时对象,进而形成大量的Gc,从而形成卡顿
简单示例:
//定义一个含有Runtime 运行时注解的 注解,经过反射和运行时获取它的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface metaTest {
int value() default 100;
}
//经过反射获取到运行时的metaTest的注解
public class ReflactMain {
@metaTest(10)
public int age;
public static void main(String[] args) {
ReflactMain mian = new ReflactMain();
metaTest testInterface = null;
try {
//一、首先获取到类的Class 类型
Class clazz = mian.getClass();
//二、经过class类型获取到对应的field 对象
Field field = clazz.getField("age”);
//三、经过field、method的getAnnotation方法获取到注解的方法
testInterface = field.getAnnotation(metaTest.class);
//四、直接能够经过注解内定义的方法获取注解内的值
System.out.println("==:" + testInterface.value());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
解析:经过反射得到field 这个变量,再经过内部的getAnnotation()方法获取到注解的方法
5、ButterKnife的工做原理
经过APT注解处理器生成Java代码以后,再编译成.class文件
一、编译的时候扫描注解,并作相应的处理,生成java代码,生成java代码时是调用javapoet库生成的
ps:同时经过其余类@Bind/@Click这类注解 在编译的时候动态生成须要的java文件;生成java文件后,编译器会对应的生成Class文件
二、调用ButterKnife.bind( this )的时候,将Id与对应的上下文绑定在一块儿
ps:完成findViewById、setOnClickListene 等过程
ButterKnifeProcessor extends AbstractProcessor
几个辅助的方法 init() 、getSupportedAnnotationTypes()、getSupportedAnnotations()
@Override
public synchronized void init(ProcessingEnvironment env) {
- 该方法会在初始化的时候调用一次,用来获取一些辅助的工具类
- 经过synchronized关键字保证获取到的对象都是单例的
- ProcessingEnvironment 做为参数能够提供几个有用的工具类
//BK process在运行的时候会扫描Java 原文件,每个java原文件的每个独立的部分就是一个element,而后经过elementUtils对每个element进行解析
elementUtils = env.getElementUtils();
//用于处理TypeElement
typeUtils = env.getTypeUtils();
//建立生成的辅助文件所用
filer = env.getFiler();
}
//返回所支持的注解类型
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
//确认butterknife 定义了哪些注解可使用
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
return annotations;
}
重要方法:
拿到全部的注解信息存储到一个Map<TypeElement,BindingSet>集合当中,
而后遍历map集合作相应的处理,最后生成需求的代码
process():处理注解
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
findAndParseTargets(): 针对每个自定义好的注解
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
…
// Process each @BindView element.以BindView 为例子:
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
//进行转化
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
}
parseBindView():
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
如下内容:拿到注解信息并验证是不是xx的子类,集中处理注解所须要的内容并保存到一个map集合当中
//建立一个原文件所对应的对象的element元素所对应的类型
TypeElementenclosingElement = (TypeElement) element.getEnclosingElement();
//判断是否在被注解的属性上,若是是private 或是static 修饰的注解就会返回一个hasError false值 ,
同时包名是以android 或是java开头的也会出错
boolean hasError =
isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
...
//一些父类的信息用TypeElement获取不到须要经过TypeMirror 来获取,并经过asType()验证是不是需求的子类
TypeMirror elementType = element.asType();
...
//判断里面的元素是不是view 以及其子类或是是不是接口 ,若是不是view的继承类没有意义再往下进行
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
...
}
…
//获取要绑定的View的 Id ,经过getAnnotation(BindView.class).value
int id = element.getAnnotation(BindView.class).value();
//传入TypeElement的值,根据所在的元素查找Build
BindingSet.Builder builder = builderMap.get(enclosingElement);
//若是相应的build已经存在了
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(id));
if (existingBindingName != null) {
//若是name 不为空 ,则说明已经被绑定过了就会报错并返回
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
//建立一个新的builder
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
…
//经过addField()方法添加到集合当中
builder.addField(getId(id), new FieldViewBinding(name, type, required));
}
//将Id 和 View 进行绑定,传入到一个map集合中
addField————>getOrCreateViewBindings()————>getOrCreateViewBindings():
private ViewBinding.Builder getOrCreateViewBindings(Id id) {
ViewBinding.Builder viewId = viewIdMap.get(id);
if (viewId == null) {
viewId = new ViewBinding.Builder(id);
viewIdMap.put(id, viewId);
}
return viewId;
}
//生成java代码 ,返回一个JavaFile对象用来转编译成java代码
JavaFile brewJava(int sdk) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
//
com.squareup.javapoet 第三方库来生成java代码 生成所须要的类型
private TypeSpec createType(int sdk) {
//二、经过这个库的Builder 内部类方法构建须要的属性
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC);
//三、判断属性是不是final类型的,若是是就添加final的修饰符
if (isFinal) {
result.addModifiers(FINAL);
}
//四、将绑定的集合set 集中到一块儿,绑定的集合是否为null
if (parentBinding != null) {
//五、添加一些父类的信息,好比父类的类名
result.superclass(parentBinding.bindingClassName);
} else {
//六、添加父类以上 UNBINDER这个接口做为属性添加进去
result.addSuperinterface(UNBINDER);
}
//七、当前是否已经有了TargetField,Activity的成员变量有没有被绑定到
if (hasTargetField()) {
//八、有的话给targetTypeName添加一个private属性
result.addField(targetTypeName, "target", PRIVATE);
}
//九、当前控件是不是View或是View的子类 ?或是 Activity ?or Dialog?
//是的话就添加相应的构造方法
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
//十、若是本身的构造方法不须要View的参数的话,就必需要添加一个须要View参数的构造方法!!
if (!constructorNeedsView()) {
result.addMethod(createBindingViewDelegateConstructor());
}
…
}
//十一、将注解换算成 java
代码 好比findViewById,将注解生成所须要的Java代码
createBindingConstructor(sdk));
ps:
判断是否有监听,若是有就会将View设置成final;
遍历ViewBindings把绑定过的View都遍历一遍,而后调用addViewBinding()最终 生成findViewById()函数
private MethodSpec createBindingConstructor(int sdk) {
...
//十二、是否有方法绑定
if (hasMethodBindings()) {
//1三、有方法绑定的状况,添加targetTypeName类型的参数,并设置为final类型修饰
constructor.addParameter(targetTypeName, "target", FINAL);
} else {
//1四、无方法绑定的状况,依然添加一个targetTypeName类型的参数,区别与有方法绑定的就是不须要final修饰
constructor.addParameter(targetTypeName, "target");
}
//1五、有注解的View
if (constructorNeedsView()) {
//1六、存在已经添加了注解的View的话,就须要给其添加一个View类型的source参数
constructor.addParameter(VIEW, "source");
} else {
//1七、不存在已经添加了注解的View的话,就须要给其添加一个View类型的context参数
constructor.addParameter(CONTEXT, "context");
}
…
//1八、若是调用了@OnTouch注解的话,须要添加一个SUPPRESS_LINT注解
if (hasOnTouchMethodBindings()) {
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value", "$S", "ClickableViewAccessibility")
.build());
}
…
//1九、经过addStatement添加成员变量的方法
if (hasTargetField()) {
constructor.
addStatement("this.target = target");
constructor.
addCode("\n");
}
…
//20、核心方法
for (ViewBinding binding : viewBindings) {
addViewBinding(constructor, binding);
}
addViewBinding():
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
…
//2一、经过该方法优化场景,告诉用户这里有View须要绑定 target.l 不能获取private 修饰的成员变量!!
FieldViewBinding fieldBinding = binding.getFieldBinding();
CodeBlock.Builder builder = CodeBlock.builder()
.add("
target.$L = ", fieldBinding.getName());
…
//2二、这里就是把findViewById添加到代码中
if (!requiresCast && !fieldBinding.isRequired()) {
builder.add("source.findViewById($L)", binding.getId().code);
}
}