依赖注入框架:ButterKnife 实现原理


依赖注入框架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

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值