依赖注入框架ButterKnife的使用与原理解析
https://blog.csdn.net/AndrExpert/article/details/103726082
1. 注解与依赖注入
1.1 注解
编译 类加载 运行时 Annotation注解
@interface
@元注解1
@元注解2
public @interface 注解名称
标准注解 元注解 自定义注解
Override Deprecated SuppressWarnings
SafeVarargs SafeVarargs
SafeVarargs SafeVarargs SuppressWarnings
Target ElementType.METHOD
Target ElementType . Retention
Target.
RetentionPolicy.SOURCE
public @interface Override
@Deprecated
Target(value = [
LOCAL_VARIABLE,PARAMETER
]) Retention (RetentionPolicy.Runtime)
public @interface Deprecated
@SuppressWarnings和@SafeVarargs,这四个标准注解为类、成员方法等程序元素提供基本的注解修饰功能,它们被定于在java
@SuppressWarnings(value="123")
String[] value();//元数据
@Documented
@Targe //元注解
@Retention用来声明注解的保留策略。@Targe的注解取值是一个RetentionPolicy类型
RetentionPolicy
Source Class Runtime
//通过反射获取MainActivity_ViewBinding的构造器
ElementType.Annotation_Type
ElementType[] value();
@Author(name= scdnId = )
1.1.2 注解处理器
javax.annotation.processing
比如对于运行时注解来说,通常使用反射机制实现注解处理器;
对于编译时注解会采用AbstractProcessor实现注解处理器,
APT Annotation Process Tool
由于获取注解和生成代码都是在代码编译时完成的,故比反射在运行时处理注解大大提高了程序的性能。
APT项目通常由两个Java Library模块组成:Annotation模块和Compiler模块,
其中,Annotation模块用来存放自定义的注解,而Compiler模块用来实现注解处理器,
即包含继承AbstractProcessor的类,它依赖Annotation模块。app模块的gradle中依赖如下:
JAva Library
Annotation模块 Compilier模块
Annotation模块 Compilier模块
implementation project path annotation
annotationProcessor path path compiler
get
getSupportedSourceVersion
AutoService
@AutoService(Process.class)
Elements (23)
被注解的对象是否合法 注解
RoundEnvironment roundEnvironment
可以通过该对象查找找到的注解,并返回被注解注释的元素Element集合。
Element集合
getElementsAnnotatedWith getMessager
getKind ElementKind.Field
45
element.toString
ElementKind.Field
getSupportedAnnotationTypes () {
S34
}
90
BindView.class.getCanonicalName;
.java->.class
// 由于源文件编译成class文件主要借助PC端的javac工具,因此需要指定Java版本
// 这里调用 SourceVersion.latestSupported()返回最新的Java版本即可
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
getSupportSourceVersion
latestSupported
Excutable Element
PackageElement TypeElement VariableElement(23,45)
Java Library module .gradle
tasks.withType (JavaCompile) {
}
options.encoding = "UTF-8"
依赖注入
Ioc
Inversert of Controll IoC理论
即由IoC5容器帮对象找相应的依赖对象并注入,而不是由对象主动去找
Ioc
Dependency Injection Diagnostic
所谓依赖注入,是指由IoC容器在运行期间,动态地将某种依赖关系注入到对象中
获得依赖对象的
依赖注入常见的三种实现方式:构造器注入、属性注入和接口注入,它们的区别如下:
DI Dependency Injection
IOC Inversion of Controll
ButterKnife Android系统的View依赖注入框架,,而是通过注解处理器在编译时生成新的class(注:编译时注解)
//
View依赖注入框架
apply plugin com.jakewharton.butterKnife
//编译时注解处理器
annotationProcessor compiler
BindAnim BindArray
@onCreateView onDestroyView
inflater.inflate
ButterKnife.bind this, view
onDestroyView
unbinder.unbind
ViewHolder
view.setTag holder
ButterKnife.bind this,view
-keep class **$$ViewBinder {*;}
@bufferknife .*<fields> .*<methods>
annotations\src\main\java\butterknife
buildscript
bufferknife
annotations
compiler
-gradle-plugin
-integration-test
-lint
-reflect
-runtime
gradle
sample
website
gradle-plugin
integration-test
sample website gradle lint reflect
其中,bufferknife-annotations项目实现各种注解类,比如BindView、Onclick等;butterknife-compiler项目实现注解解释器;bufferknife项目提供绑定Activity、Fragment以及事件监听器等;butter-gradle-plugin为gradle插件,剩余的项目均为辅助之用。
butterKnife-annotations
bufferknife-compiler
butterKnife-annotations
创建各类注解类
@Documented
@Target({METHOD,PARAMETER,FIELD,})
,BindView注解的成员变量value的值应为一个id资源引用。
保留策略 RUNTIME 作用域为字段
BindView注解的成员变量value的值应为一个id资源引用
value = id
@OnClick
@ListenerClass @ListenerMethod
@IdRes int[] value() default {}
@ListenerClass且包含多个成员变量
ANNOTATION_TYPE
Retention Runtime
Target
ButterKnife的注解处理器(ButterKnife的注解处理器)
前面我们说到,ButterKnife的注解处理器是在butterknife-compiler项目中实现的,
ProcessingEnvironment env
filer = env.getFiler
set<string> types
annotation.getCanonicalName
List<Class<? extend Annotation>> annotations =
Arrays.asList
getSupportedAnnotationTypes方法
annotations.add(BindAnim.class);
public @interface OnClick {
annotation = OnClick
process (Set<? extend TypeElement>)
Class<? extend Annotation> annotation
Set<? extend TypeElement> elements
RoundEnvironment env
Map<TypeElement,BindingSet>
Set<? extends TypeElement> elements,RoundEnvironment
env
Map.Entry<TypeElement,BindingSet> bindingMap.entrySet
JavaPoet
BindingSet除了保存信息目标类信息外,还封装了 JavaPoet 生成目标类代码的过程。
process方法是注解处理器的核心,它主要完成目标类信息的收集(注释1),然后生成对应的java类(注释2)。
process 是目标类信息的收集,然后生成对应的java类;
//
TypeElement:代码使用某个注解的元素,如Activity、Dialog、Fragment等
// BindingSet.Builder:创建BindingSet的建造器,其中BindingSet用于存储要生成类
// 的基本信息以及注解元素的相关信息。
//注解元素的相关信息
env.getElementsAnnotatedWith BindString.class
parseBindView builderMap,erasedTargetNames
parseResourceString element,buildMap,erasedTargetNames
VariableElement element
parseBindView
parseBindView erasedTargetNames
buildMap erasedTargetNames
ClasspathBindingSet classpathBindings =
findAllSupertypeBindings =
buildmap.entrySet
buildMap.entrySet
new ArrayDeque<>(buildMap.entrySet())
Deque<Map.entry<>>
55
// 使用builderMap.entrySet()创建一个队列
// 队列中存储的是键值对Set集合
entries.removeFirst
Map.Entry<TypeElement,BindingSet.Builder>
bindingMap. type,builder.build
BindingInformationProvider parentBinding =
BindingSet.Builder BindingSet
build.setParent(parentBinding)
bindingset.build
findAndParseTargets ()
@BindView ->@BindView
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,Set<TypeElement> erasedTargetNames) {
parseBindView ()
element.getEnclosingElement
isInaccessibleViaGeneratedCode
isBindingInWrongPackage
TypeMirror elementType
elementType.getKind == TypeKind.TYPEVAR
// 获取全限定类名(包名+类名)
getQualifiedName
getSimpleName
isSubtypeOfType View_Type
TypeKind.Error == elementType.getKind
getAnnotation BindView.class .value()
element.getAnnotation.value
elementToId element BindView.class,id)
// 注:一个BindingSet.Builder构建的BindingSet对应于一个目标类
BindingSet.Builder
builder.findExistingBindingName
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
34
enclosingElement.getQualifiedName
element.getSimpleName
BindingSet.Builder->buildmap
FieldViewBinding
builder.addField
// 记录当前元素的父类元素
erasedTargetNames.add(enclosingElement);
getOrCreateBindingBuilder
TypeElement
enclosingElement builderMap
newBuilder TypeElement enclosingElement
ACTIVITY_TYPE DIALOG_TYPE View_Type
isSubtypeOfType
typeMirror
ParameterizedTypeName targetType .rawType
enclosingElement.getQualifiedName.substring packageName
.replace
//即最终要生成的java类的名称
bindingClassName(packageName,ClassName)
Modifier.FINAL
BindingSet ButterKnife
2.2.3 ButterKnife的bind方法
ButterKnifeProcessor->AbstractProcessor
通过对注解处理ButterKnifeProcessor处理过程的分析,
我们容易知道处理器最终实现了根据注解的信息完成了被注解元素的查找和依赖注入(即根据资源ID找到view,
并赋值给目标类的指定成员属性),
并自动生成保存这些信息且以目标类_ViewBinding为命名的Java文件
ButterKnifeProcessor => AbstractProcessor
即根据资源ID找到view,并赋值给目标类的指定成员属性
ButterKnife.bind
方法来使其生效,
Unbinder bind
View sourceView =
target.getWindow.getDecorView
getClass MainActivity_ViewBinding的构造器
findBindingConstructorForClass
Constructor<? extend Binder> binder =
{
// 3.调用MainActivity_ViewBinding的构造器
// 实例化一个MainActivity_ViewBinding对象
return constructor.newInstance(target, source);
constructor.newInstance(target,source)
@UiThread @CheckResult @Nullable
Class<?> cls
get cls BINDINGS
// 2.获取Class对象的全限定名称(包名+类型)
cls.getName
// 4.通过反射获取MainActivity_ViewBinding的构造器
bindingClass.getConstructor cls,View.class
bindingClass.getConstructor cls,View.class
cls.getSuperclass
最后,我们再来看下MainActivity_ViewBinding的构造方法中是
如何实现对View进行依赖注入的。
MainActivity_ViewBinding的构造方法中是如何实现对View进行
依赖注入的。
@UiThread
MainActivity_ViewBinding
findRequiredViewType
findRequiredView
castView
DebouncingOnClickListener
doClick target.onViewClick
@CallSuper
MainActivity_ViewBinding 类的构造方法中
在MainActivity_ViewBinding类的构造方法中,它会根据控件的资源ID获取对应的控件的实例,然后将其赋值(注入)给MainActivity类中的mTvTip和mBtn属性。其中,根据控件的资源ID获取对应的控件的实例通过调用findRequiredViewAsType方法实现,源码如下:
// 类型转换
return cls.cast(view);
cls.cast view
最后,我们再对ButterKnife框架的实现原理作个小结:
ButterKnife从严格意义讲不算是依赖注入框架,
它只是专注于Android系统的View注入框架,
并不支持其他方法的注入,
它可以减少大量的findViewById以及setOnClickListener代码,
简化代码并提升开发效率。ButterKnife的核心是借助了annotationProcessor 和JavaPoet技术,
其中annotationProcessor 注解处理器用于解析注解元素并收集要生成目标类的信息,
并得到View的依赖注入的类源码;JavaPoet技术将实现将类代码生成文件,
避免了我们自己去拼接字符串来实现。由于ButterKnife整个过程是在项目编译阶段完成的,
因此对项目的运行时的性能没有影响。
annotationProcessor JavaPoet