依赖注入和Butter Knife源码分析
一、控制反转和依赖注入:
两图的区别:
(1)图一有高度的耦合性,多个对象相互依赖,如果ObjectA想让ObjectB跟随自己转动应该怎么做?
在ObjectA中创建一个ObjectB的对象,然后调用ObjectB的某个方法进行转动。ObjectA主动控制ObjectB的创建和行为。
(2)图二在ABCD中创建了一个第三方容器,第三方容器控制ObjectA、B、C、D的创建和转动。如果ObjectA想让ObjectB跟随自己转动应该怎么做?
A告诉第三方容器,让第三方容器去创建ObjectB,并且让ObjectB进行转动。
(3)对比:图一控制权在自身,图二控制权在IOC容器,降低了多个对象之间的耦合性。这就是控制反转的思想。
1、控制反转(IOC):
Ioc—Inversion of Control,即“控制反转”。控制反转是种设计思想,将你设计好的对象交给容器控制,而不是在你对象内部直接控制。
* 控制什么?控制了外部资源获取,比如对象创建、文件读取等。
* 什么反转了?依赖对象的获取反转了。ObjectB依赖于ObjectA。之前是ObjectA想获取ObjectB由自身控制,现在ObjectA获取ObjectB由IOC容器控制。
2、依赖注入(DI):
DI—Dependency Injection,即“依赖注入”。组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。
谁依赖谁?某个对象依赖于IOC容器
谁注入谁?IOC容器提供资源,将资源注入到某个需要的对象中。
3、控制反转和依赖注入的关系:
同一思想的不同说法。资源获取的主动索取变成被注入。
4、依赖注入的实现:
* 基于接口:定义接口,提供给IOC容器来传入自身所需要的对象
* 基于set方法:通过方法提供给IOC容器来传入自身所需要的对象
* 基于构造函数:通过构造提供给IOC容器来传入自身所需要的对象
* 基于注解:通过注解,生成方法,给IOC容器来传入自身所需要的对象
三、ButterKnife使用:
1、build.gradle:
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
2、调用:
@BindView(R.id.tv_title)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
四、ButterKnife 源码分析:
1、代码结构
核心代码:
* butterknife 提供一些api
* butterknife-annotations 提供一些自定义注解
* butterknife-compiler 注解解析器
2、BindView 注解
@Retention(CLASS)
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
@Retention(CLASS)说明是编译时注解。@Target(FIELD)说明注解作用于成员变量。
如何将注解解析生成Java文件呢?
3、注解解析器 ButterKnifeProcessor:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//(1)查找所有的注解并进行解析
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//(2)进行遍历,获取注解的值
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//(3)将注解的类生成一个JavaFile,并输出为一个Java文件
JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
}
(1)findAndParseTargets:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
......
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
processing rounds
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) {
......
//获取注解标注的值
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
Id resourceId = elementToId(element, BindView.class, id);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(resourceId);
if (existingBindingName != null) {
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 = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = simpleName.toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
//将值存在到field上
builder.addField(resourceId, new FieldViewBinding(name, type, required));
}
解析注解的过程:将注解进行解析,获取到注解的值,然后将注解的类生成一个Java文件。Java文件在哪?
4、编译后生成文件:
通过注解解析器将注解解析生成java 文件
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(MainActivity target, View source) {
this.target = target;
target.mTextView = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'mTextView'", TextView.class);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTextView = null;
}
}
(1)查找id为tv_title的TextView:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
...
}
最终还是通过source.findViewById 去查找view。传入的source的View当前的Activity的DecorView。
5、ButterKnife.bind(this):
ButterKnife的bind方法:
@NonNull @UiThread
public static Unbinder bind(@NonNull Dialog target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
获取到了当前Activit的DecorView,然后调用了createBinding方法。
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
......
//去获取constructor 对象
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//通过反射区实例化constructor对象
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
......
}
findBindingConstructorForClass方法:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//(1)BINDINGS是一个LinkedHashMap的集合,先在集合取cls对应的Constructor实例
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
//(2)取到了就直接返回
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//(3)获取不到就通过反射就创建,就是刚刚的MainActivity_ViewBinding的辅助类
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
//将该Constructor的实例作为value,存入集合中
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
总结:在MainActivity中通过BindView对TextView进行注解
(1)在编译时期通过注解解析器ButterKnifeProcessor对注解进行解析,获取到注解标注的值,然后生成一个MainActivity_ViewBinding的类。
(2)通过调用ButterKnife.bind(this)来绑定上下文。会从一个LinkedHashMap的集合中取对应MainActivity的Constructor对象,如果取不到就通过反射创建,然后存到集合中。
(3)同时通过反射创建了MainActivity_ViewBinding的对象,此时传入了MainActivity上下文对象,在MainActivity_ViewBinding中可以通过DecorView 去获取被注解的TextView。
下一篇: ThinkPHP的增、删、改、查