注解学习之注解基础
注解学习之依赖注入框架ButterKnife
ButterKnife使用方式
在Module:app build.gradle 中添加如下代码
dependencies {
implementation 'com.jakewharton:butterknife:10.1.0'
//作用:在编译时处理注解,生成辅助文件,提升应用性能
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
ButterKnife GitHub链接 在GitHub中可以查看完成的源码,在项目中看不到butterKnife-compiler的源码
在项目中使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.open_search_image)
Button openSearchImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//******* 这里一般用插件工具的话会自动生成
ButterKnife.bind(this);
initListener();
}
//这里会自动给各个View注入监听事件的依赖
@OnClick({R.id.iv_back,R.id.open_search_image})
public void onClick(View view){
switch (view.getId()){
case R.id.open_search_image:
break;
case R.id.iv_back:
break;
default:
break;
}
}
private void initListener() {
openSearchImage.setOnClickListener(v -> SearchImageActivity.startSearchImageActivity(MainActivity.this));
}
}
ButterKnife支持的注解还是挺多的,自己可以点开源码看一看哦
流程分析
编译时生成依赖注入辅助文件
生成文件主要是在ButterKnifeProcess 的process方法
- TypeElement: 绑定了注解的各个类 BindSet:各个类的辅助:包含添加了注解的属性
- 遍历绑定了注解类的集合
- 生成Java文件
- 写入文件
源文件
public class SearchImageActivity extends AppCompatActivity implements IBaseUIView {
@BindView(R.id.iv_back)
ImageView ivBack;
}
生成的辅助文件
public class SearchImageActivity_ViewBinding implements Unbinder {
private SearchImageActivity target;
@UiThread
public SearchImageActivity_ViewBinding(SearchImageActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public SearchImageActivity_ViewBinding(SearchImageActivity target, View source) {
this.target = target;
target.ivBack = Utils.findRequiredViewAsType(source, R.id.iv_back, "field 'ivBack'", ImageView.class);
...
}
@Override
@CallSuper
public void unbind() {
SearchImageActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.ivBack = null;
...
}
}
ButterKnifeProcess类 process() 处理流程
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//TODO 1. TypeElement: 绑定了注解的各个类 BindSet:各个类的辅助:包含添加了注解的属性
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
//TODO 2.遍历绑定了注解类的集合
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
//获取类元素
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
//TODO 3.生成Java文件
JavaFile javaFile = binding.brewJava(sdk, debuggable);
try {
//TODO 4.写入文件
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
这里分析一下BindView其他的都大同小异 ,parseBindView
1.findAndParseTargets
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
...
// 处理每一个绑定了@BindView的元素
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
try {
//1.对每一个元素进行处理
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
...
}
看一下parseBindView: 找到个元素的上一级并把这元素添加到上一级中,在这里就是把绑定了BindView注解的属性,添加到它所在的类中去
- 返回这个元素的上一级元素(包裹它的元素)
- 返回一个类的BindingSet.Builder,已经有了就用缓存的
- 给一个类添加属性
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
//TODO 1.返回这个元素的上一级元素(包裹它的元素)
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
...
if (hasError) {
return;
}
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
//TODO 2.返回一个类的BindingSet.Builder,已经有了就用缓存的
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);
//TODO 3.给一个类添加属性
builder.addField(resourceId, new FieldViewBinding(name, type, required));
//TODO 4.添加了ViewBind注解的类的集合
erasedTargetNames.add(enclosingElement);
}
2.生成Java文件
JavaFile brewJava(int sdk, boolean debuggable) {
//TODO 1.生成文件所需要的信息
TypeSpec bindingConfiguration = createType(sdk, debuggable);
//TODO 2.生成文件
return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
生成文件所需要的信息
private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(PUBLIC)
.addOriginatingElement(enclosingElement);
//TODO 1.判断是不是final的
if (isFinal) {
result.addModifiers(FINAL);
}
//TODO 2.判断有没有父类
if (parentBinding != null) {
result.superclass(parentBinding.getBindingClassName());
} else {
result.addSuperinterface(UNBINDER);
}
if (hasTargetField()) {
result.addField(targetTypeName, "target", PRIVATE);
}
//TODO 3.添加构造方法
if (isView) {
result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
// Add a delegating constructor with a target type + view signature for reflective use.
result.addMethod(createBindingViewDelegateConstructor());
}
//TODO 4.添加这个类的属性、方法等信息
result.addMethod(createBindingConstructor(sdk, debuggable));
if (hasViewBindings() || parentBinding == null) {
result.addMethod(createBindingUnbindMethod(result));
}
return result.build();
}
到这里整个生成辅助文件的过程就分析完了
依赖注入流程
1.在Activity中调用绑定
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search_image);
//添加绑定
ButterKnife.bind(this);
}
2.调用ButterKnife中的Bind(@NonNull Activity target)
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
//获取view
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
继续往下看 获取 SearchImageActivity_ViewBinding 构造函数,并执行
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
//获取 SearchImageActivity_ViewBinding 构造函数
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
...
try {
//执行构造函数
return constructor.newInstance(target, source);
}
}
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
try {
//关键在这里,找到上面生成的辅助文件
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
return bindingCtor;
}
找到编译时生成的辅助文件
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
3.执行辅助文件(SearchImageActivity_ViewBinding)的构造函数执行
@UiThread
public SearchImageActivity_ViewBinding(SearchImageActivity target, View source) {
this.target = target;
//给目标对象赋值或者注入依赖
target.ivBack = Utils.findRequiredViewAsType(source, R.id.iv_back, "field 'ivBack'", ImageView.class);
}
4.查询View
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);
}
//通过View查询
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
至此正整个ButterKnife的流程就分析完毕了,建议大家自己阅读一下源码,以加深理解与运用