编译时注解Butterknife源码解析之深入篇刘永雷.docx
- 文档编号:9532480
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:31
- 大小:32.36KB
编译时注解Butterknife源码解析之深入篇刘永雷.docx
《编译时注解Butterknife源码解析之深入篇刘永雷.docx》由会员分享,可在线阅读,更多相关《编译时注解Butterknife源码解析之深入篇刘永雷.docx(31页珍藏版)》请在冰豆网上搜索。
编译时注解Butterknife源码解析之深入篇刘永雷
编译时注解Butterknife源码解析之深入篇(雷惊风)
1.概述。
上篇文章我对Butterknife实现做了一些基础的说明,本篇文章我将向大家详细分析@BindView、@OnClick解析流程、生成BindingSet对应Java文件流程及我们调用ButterKnife.bind(this)后ButterKnife与生成Java文件的建立连接过程。
2.@BindView解析流程。
这篇文章将接着上篇文章的findAndParseTargets(RoundEnvironmentenv)方法,如果你看了上篇文章,那么你就知道,所有Butterknife中注解的解析都是在这个方法中的。
好的那么我们开始吧。
我们这个方法的流程上篇文章已经说过了,这里就不再浪费口水了,我们把解析@BindView的代码拷出来再看一下:
//Processeach@BindViewelement.
/**
*处理BindView(R.id.btn)注解;
*1.做了一系列验证;如:
privatestatic修饰符判断、@BindView必须作用在View上或者interface上等等;
*2.id的处理;生成QualifiedId、缓存等;
*3.builder创建;一个id是否注解了多次
*4.@Nullable处理,创建FieldViewBinding存入builder,将enclosingElement存入erasedTargetNames;
*/
for(Elementelement:
env.getElementsAnnotatedWith(BindView.class)){
//wedon'tSuperficialValidation.validateElement(element)
//sothatanunresolvedViewtypecanbegeneratedbylaterprocessingrounds
try{
parseBindView(element,builderMap,erasedTargetNames);
}catch(Exceptione){
logParsingError(element,BindView.class,e);
}
}
很简单,获取了项目中所有的被@BindView注解了的Element,通过for()循环处理每一个,一个try{}catch{}代码块,try中出问题,打印信息,关于在注解处理器中处理错误log信息,在这里就不讲了,可以在init()方法中获取Messager辅助类。
看来重点在parseBindView(element,builderMap,erasedTargetNames);方法中了,跟进去:
privatevoidparseBindView(Elementelement,Map
Set
//获取父级Element;
TypeElementenclosingElement=(TypeElement)element.getEnclosingElement();
//Startbyverifyingcommongeneratedcoderestrictions.
booleanhasError=isInaccessibleViaGeneratedCode(BindView.class,"fields",element)
||isBindingInWrongPackage(BindView.class,element);
//VerifythatthetargettypeextendsfromView.
TypeMirrorelementType=element.asType();
if(elementType.getKind()==TypeKind.TYPEVAR){
TypeVariabletypeVariable=(TypeVariable)elementType;
elementType=typeVariable.getUpperBound();
}
NamequalifiedName=enclosingElement.getQualifiedName();
NamesimpleName=element.getSimpleName();
//@BindView必须作用在View上或者interface上;
if(!
isSubtypeOfType(elementType,VIEW_TYPE)&&!
isInterface(elementType)){
if(elementType.getKind()==TypeKind.ERROR){
note(element,"@%sfieldwithunresolvedtype(%s)"
+"mustelsewherebegeneratedasaVieworinterface.(%s.%s)",
BindView.class.getSimpleName(),elementType,qualifiedName,simpleName);
}else{
error(element,"@%sfieldsmustextendfromVieworbeaninterface.(%s.%s)",
BindView.class.getSimpleName(),qualifiedName,simpleName);
hasError=true;
}
}
if(hasError){
return;
}
//Assembleinformationonthefield.
intid=element.getAnnotation(BindView.class).value();
//通过enclosingElement获取builder,每一个builder对应一个类,如activity;
BindingSet.Builderbuilder=builderMap.get(enclosingElement);
//将element所在包与id封装到QualifiedId中;
QualifiedIdqualifiedId=elementToQualifiedId(element,id);
if(builder!
=null){
//判断当前@BindView所修饰控件是否已经绑定过;
//getId():
将id存入Id对象,并存入symbols;
StringexistingBindingName=builder.findExistingBindingName(getId(qualifiedId));
if(existingBindingName!
=null){
error(element,"Attempttouse@%sforanalreadyboundID%don'%s'.(%s.%s)",
BindView.class.getSimpleName(),id,existingBindingName,
enclosingElement.getQualifiedName(),element.getSimpleName());
return;
}
}else{
//新建一个builder;
builder=getOrCreateBindingBuilder(builderMap,enclosingElement);
}
Stringname=simpleName.toString();
TypeNametype=TypeName.get(elementType);
//判断是否添加了@Nullable
booleanrequired=isFieldRequired(element);
//通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding)
builder.addField(getId(qualifiedId),newFieldViewBinding(name,type,required));
//Addthetype-erasedversiontothevalidbindingtargetsset.
erasedTargetNames.add(enclosingElement);
}
方法也还好,不到100行,来看看在这个方法中做了一些什么操作吧。
咱们先拿出一部分来看:
//获取父级Element;
TypeElementenclosingElement=(TypeElement)element.getEnclosingElement();
//Startbyverifyingcommongeneratedcoderestrictions.
booleanhasError=isInaccessibleViaGeneratedCode(BindView.class,"fields",element)
||isBindingInWrongPackage(BindView.class,element);
//VerifythatthetargettypeextendsfromView.
TypeMirrorelementType=element.asType();
if(elementType.getKind()==TypeKind.TYPEVAR){
TypeVariabletypeVariable=(TypeVariable)elementType;
elementType=typeVariable.getUpperBound();
}
NamequalifiedName=enclosingElement.getQualifiedName();
NamesimpleName=element.getSimpleName();
//@BindView必须作用在View上或者interface上;
if(!
isSubtypeOfType(elementType,VIEW_TYPE)&&!
isInterface(elementType)){
if(elementType.getKind()==TypeKind.ERROR){
note(element,"@%sfieldwithunresolvedtype(%s)"
+"mustelsewherebegeneratedasaVieworinterface.(%s.%s)",
BindView.class.getSimpleName(),elementType,qualifiedName,simpleName);
}else{
error(element,"@%sfieldsmustextendfromVieworbeaninterface.(%s.%s)",
BindView.class.getSimpleName(),qualifiedName,simpleName);
hasError=true;
}
}
if(hasError){
return;
}
这部分是对我们@BindView注解应用的一个正确性的一个检查,首先获取了我们的element对应的外部类的TypeElement,比如,activity中用@BindView注解了一个Buttonbtn;获取了activity对应的TypeElement,如果你看了上篇文章你就会明白。
然后调用了一个isInaccessibleViaGeneratedCode(BindView.class,"fields",element)方法与一个isBindingInWrongPackage(BindView.class,element)方法进行判断操作。
一个一个来看一下,第一个:
/**
*检查annotation作用域是否正确;
*
*@paramannotationClass
*@paramtargetThing
*@paramelement
*@return
*/
privatebooleanisInaccessibleViaGeneratedCode(Class
extendsAnnotation>annotationClass,
StringtargetThing,Elementelement){
booleanhasError=false;
//获取当前element所在类的TypeElement;
TypeElementenclosingElement=(TypeElement)element.getEnclosingElement();
//Verifymethodmodifiers.获取当前element的修饰符;
Set
//修饰符不能是private或者static的,否则报告异常(error方法);
//Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。
//它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。
if(modifiers.contains(PRIVATE)||modifiers.contains(STATIC)){
error(element,"@%s%smustnotbeprivateorstatic.(%s.%s)",
annotationClass.getSimpleName(),targetThing,enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError=true;
}
//Verifycontainingtype.element只能直接从属于类(不能修饰局部变量);否则报错;
if(enclosingElement.getKind()!
=CLASS){
error(enclosingElement,"@%s%smayonlybecontainedinclasses.(%s.%s)",
annotationClass.getSimpleName(),targetThing,enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError=true;
}
//Verifycontainingclassvisibilityisnotprivate.element外层类不能是私有的,否则报错;
if(enclosingElement.getModifiers().contains(PRIVATE)){
error(enclosingElement,"@%s%smaynotbecontainedinprivateclasses.(%s.%s)",
annotationClass.getSimpleName(),targetThing,enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError=true;
}
returnhasError;
}
在这个类中判断了三种非正常情况:
1.@BindView注解的Element为private或者static修饰报错,如下例子:
@BindView(R.id.btn)
privateButtonbtn;
这种情况下就会报错。
2.当前Element不是直接在一个类里边,报错。
如:
在一个方法的局部变量上添加了注解。
3.外部Element是private的报错。
如:
privateclassActivityextends...{
@BindView(R.id.btn)
privateButtonbtn;
}
再看一下isBindingInWrongPackage(BindView.class,element)方法:
/**
*检查anitation注解是否作用在了系统类上;
*
*@paramannotationClass
*@paramelement
*@return
*/
privatebooleanisBindingInWrongPackage(Class
extendsAnnotation>annotationClass,
Elementelement){
TypeElementenclosingElement=(TypeElement)element.getEnclosingElement();
StringqualifiedName=enclosingElement.getQualifiedName().toString();
//不能作用于Android系统类里;
if(qualifiedName.startsWith("android.")){
error(element,"@%s-annotatedclassincorrectlyinAndroidframeworkpackage.(%s)",
annotationClass.getSimpleName(),qualifiedName);
returntrue;
}
//不能作用在java系统类中;
if(qualifiedName.startsWith("java.")){
error(element,"@%s-annotatedclassincorrectlyinJavaframeworkpackage.(%s)",
annotationClass.getSimpleName(),qualifiedName);
returntrue;
}
returnfalse;
}
这个方法主要是检查我们是否用到了系统类上,这里注意我们自己定义的包名。
接着回到上一个方法向下看,后续又检查了是否用在了View子类上或者Interface上。
我都加了注释,不在详细解释。
如果上边检查有一步出问题,则return终止。
到这里,检查我们应用@BindView合法性就完了。
再往下边走:
//Assembleinformationonthefield.
intid=element.getAnnotation(BindView.class).value();
//通过enclosingElement获取builder,每一个builder对应一个类,如activity;
BindingSet.Builderbuilder=builderMap.get(enclosingElement);
//将element所在包与id封装到QualifiedId中;
QualifiedIdqualifiedId=elementToQualifiedId(element,id);
if(builder!
=null){
//判断当前@BindView所修饰控件是否已经绑定过;
//getId():
将id存入Id对象,并存入symbols;
StringexistingBindingName=builder.findExistingBindingName(getId(qualifiedId));
if(existingBindingName!
=null){
error(element,"Attempttouse@%sforanalreadyboundID%don'%s'.(%s.%s)",
BindView.class.getSimpleName(),id,existingBindingName,
enclosingElement.getQualifiedName(),element.getSimpleName());
return;
}
}else{
//新建一个builder;
builder=getOrCreateBindingBuilder(builderMap,enclosingElement);
}
首先获取到我们注解中指定的id,如下代码中的R.id.btn:
@BindView(R.id.btn)
Buttonbtn;
查看builderMap中是否已经缓存了外部Element对应的BindingSet.Builder,通过id创建QualifiedId,看一下这个过程:
privateQualifiedIdelementToQualifiedId(Elementelement,intid){
returnnewQualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(),id);
}
这里用到了辅助类elementUtils获取element的包名,通过包名与id创建了QualifiedId
后边是builder判断,如果不为空,通过QualifiedId生成Id判断是否注解过相同id,注解过,则输出错误信息。
Builder为空,调用getOrCreateBindingBuilder(builderMap,enclosingElement)创建或获取builder,看一下:
privateBindingSet.BuildergetOrCreateBindingBuilder(
Map
BindingSet.Builderbuilder=builderMap.get(enclosingElement);
if(builder==null){
//生成一个builder,
//builder中保存了泛型信息、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部;
builder=BindingSet.newB
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 编译 注解 Butterknife 源码 解析 深入 篇刘永雷