




已阅读5页,还剩24页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
编译时注解Butterknife源码解析之深入篇(雷惊风)1.概述。上篇文章我对Butterknife实现做了一些基础的说明,本篇文章我将向大家详细分析BindView、OnClick解析流程、生成BindingSet对应Java文件流程及我们调用ButterKnife.bind(this)后ButterKnife与生成Java文件的建立连接过程。2.BindView解析流程。这篇文章将接着上篇文章的findAndParseTargets(RoundEnvironment env)方法,如果你看了上篇文章,那么你就知道,所有Butterknife中注解的解析都是在这个方法中的。好的那么我们开始吧。我们这个方法的流程上篇文章已经说过了,这里就不再浪费口水了,我们把解析BindView的代码拷出来再看一下:/ Process each BindView element./* * 处理BindView(R.id.btn)注解; * 1.做了一系列验证;如:private static修饰符判断、BindView必须作用在View上或者interface上等等; * 2.id的处理;生成QualifiedId、缓存等; * 3.builder创建;一个id是否注解了多次 * 4.Nullable处理,创建FieldViewBinding存入builder,将enclosingElement存入erasedTargetNames; */for (Element element : env.getElementsAnnotatedWith(BindView.class) / we dont SuperficialValidation.validateElement(element) / so that an unresolved View type can be generated by later processing rounds try parseBindView(element, builderMap, erasedTargetNames); catch (Exception e) logParsingError(element, BindView.class, e); 很简单,获取了项目中所有的被BindView注解了的Element,通过for()循环处理每一个,一个trycatch代码块,try中出问题,打印信息,关于在注解处理器中处理错误log信息,在这里就不讲了,可以在init()方法中获取Messager辅助类。看来重点在parseBindView(element, builderMap, erasedTargetNames);方法中了,跟进去:private void parseBindView(Element element, Map builderMap, Set erasedTargetNames) /获取父级Element; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); / Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, fields, element) | isBindingInWrongPackage(BindView.class, element); / Verify that the target type extends from View. TypeMirror elementType = element.asType(); if (elementType.getKind() = TypeKind.TYPEVAR) TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); Name qualifiedName = enclosingElement.getQualifiedName(); Name simpleName = element.getSimpleName(); /BindView必须作用在View上或者interface上; if (!isSubtypeOfType(elementType, VIEW_TYPE) & !isInterface(elementType) if (elementType.getKind() = TypeKind.ERROR) note(element, %s field with unresolved type (%s) + must elsewhere be generated as a View or interface. (%s.%s), BindView.class.getSimpleName(), elementType, qualifiedName, simpleName); else error(element, %s fields must extend from View or be an interface. (%s.%s), BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; if (hasError) return; / Assemble information on the field. int id = element.getAnnotation(BindView.class).value(); /通过enclosingElement获取builder,每一个builder对应一个类,如activity; BindingSet.Builder builder = builderMap.get(enclosingElement); /将element所在包与id封装到QualifiedId中; QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) /判断当前BindView所修饰控件是否已经绑定过; /getId():将id存入Id对象,并存入symbols; String existingBindingName = builder.findExistingBindingName(getId(qualifiedId); 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; builder = getOrCreateBindingBuilder(builderMap, enclosingElement); String name = simpleName.toString(); TypeName type = TypeName.get(elementType); /判断是否添加了Nullable boolean required = isFieldRequired(element); /通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding) builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required); / Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement);方法也还好,不到100行,来看看在这个方法中做了一些什么操作吧。咱们先拿出一部分来看:/获取父级Element;TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();/ Start by verifying common generated code restrictions.boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, fields, element) | isBindingInWrongPackage(BindView.class, element);/ Verify that the target type extends from View.TypeMirror elementType = element.asType();if (elementType.getKind() = TypeKind.TYPEVAR) TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound();Name qualifiedName = enclosingElement.getQualifiedName();Name simpleName = element.getSimpleName();/BindView必须作用在View上或者interface上;if (!isSubtypeOfType(elementType, VIEW_TYPE) & !isInterface(elementType) if (elementType.getKind() = TypeKind.ERROR) note(element, %s field with unresolved type (%s) + must elsewhere be generated as a View or interface. (%s.%s), BindView.class.getSimpleName(), elementType, qualifiedName, simpleName); else error(element, %s fields must extend from View or be an interface. (%s.%s), BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; if (hasError) return;这部分是对我们BindView注解应用的一个正确性的一个检查,首先获取了我们的element对应的外部类的TypeElement,比如,activity中用BindView注解了一个Button btn;获取了activity对应的TypeElement,如果你看了上篇文章你就会明白。然后调用了一个isInaccessibleViaGeneratedCode(BindView.class, fields, element)方法与一个isBindingInWrongPackage(BindView.class, element)方法进行判断操作。一个一个来看一下,第一个:/* * 检查annotation作用域是否正确; * * param annotationClass * param targetThing * param element * return */private boolean isInaccessibleViaGeneratedCode(Class annotationClass, String targetThing, Element element) boolean hasError = false; /获取当前element所在类的TypeElement; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); / Verify method modifiers.获取当前element的修饰符; Set modifiers = element.getModifiers(); /修饰符不能是private或者static的,否则报告异常(error方法); / Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。 / 它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。 if (modifiers.contains(PRIVATE) | modifiers.contains(STATIC) error(element, %s %s must not be private or static. (%s.%s), annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName(); hasError = true; / Verify containing type.element只能直接从属于类(不能修饰局部变量);否则报错; if (enclosingElement.getKind() != CLASS) error(enclosingElement, %s %s may only be contained in classes. (%s.%s), annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName(); hasError = true; / Verify containing class visibility is not private.element外层类不能是私有的,否则报错; if (enclosingElement.getModifiers().contains(PRIVATE) error(enclosingElement, %s %s may not be contained in private classes. (%s.%s), annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName(); hasError = true; return hasError;在这个类中判断了三种非正常情况:1. BindView注解的Element为private或者static修饰报错,如下例子: BindView(R.id.btn) private Button btn; 这种情况下就会报错。2. 当前Element不是直接在一个类里边,报错。如:在一个方法的局部变量上添加了注解。3. 外部Element是private的报错。如:private class Activity extends . BindView(R.id.btn) private Button btn;再看一下isBindingInWrongPackage(BindView.class, element)方法:/* * 检查anitation注解是否作用在了系统类上; * * param annotationClass * param element * return */private boolean isBindingInWrongPackage(Class annotationClass, Element element) TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String qualifiedName = enclosingElement.getQualifiedName().toString(); /不能作用于Android系统类里; if (qualifiedName.startsWith(android.) error(element, %s-annotated class incorrectly in Android framework package. (%s), annotationClass.getSimpleName(), qualifiedName); return true; /不能作用在java系统类中; if (qualifiedName.startsWith(java.) error(element, %s-annotated class incorrectly in Java framework package. (%s), annotationClass.getSimpleName(), qualifiedName); return true; return false;这个方法主要是检查我们是否用到了系统类上,这里注意我们自己定义的包名。接着回到上一个方法向下看,后续又检查了是否用在了View子类上或者Interface上。我都加了注释,不在详细解释。如果上边检查有一步出问题,则return终止。到这里,检查我们应用BindView合法性就完了。再往下边走:/ Assemble information on the id = element.getAnnotation(BindView.class).value();/通过enclosingElement获取builder,每一个builder对应一个类,如activity;BindingSet.Builder builder = builderMap.get(enclosingElement);/将element所在包与id封装到QualifiedId中;QualifiedId qualifiedId = elementToQualifiedId(element, id);if (builder != null) /判断当前BindView所修饰控件是否已经绑定过; /getId():将id存入Id对象,并存入symbols; String existingBindingName = builder.findExistingBindingName(getId(qualifiedId); 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; builder = getOrCreateBindingBuilder(builderMap, enclosingElement);首先获取到我们注解中指定的id,如下代码中的R.id.btn:BindView(R.id.btn)Button btn;查看builderMap中是否已经缓存了外部Element对应的BindingSet.Builder,通过id创建QualifiedId,看一下这个过程:private QualifiedId elementToQualifiedId(Element element, int id) return new QualifiedId(elementUtils.getPackageOf(element).getQualifiedName().toString(), id);这里用到了辅助类elementUtils获取element的包名,通过包名与id创建了QualifiedId后边是builder判断,如果不为空,通过QualifiedId生成Id判断是否注解过相同id,注解过,则输出错误信息。Builder为空,调用getOrCreateBindingBuilder(builderMap, enclosingElement)创建或获取builder,看一下:private BindingSet.Builder getOrCreateBindingBuilder( Map builderMap, TypeElement enclosingElement) BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder = null) /生成一个builder, / builder中保存了泛型信息、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部; builder = BindingSet.newBuilder(enclosingElement); builderMap.put(enclosingElement, builder); return builder;如果我们的builderMap中已经存在,直接返回,不存在调用BindingSet.newBuilder()创建并保存到builderMap中。创建保存这步是关键,看一下:static Builder newBuilder(TypeElement enclosingElement) TypeMirror typeMirror = enclosingElement.asType(); /判断类型; boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE); boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE); boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE); /泛型处理; TypeName targetType = TypeName.get(typeMirror); if (targetType instanceof ParameterizedTypeName) targetType = (ParameterizedTypeName) targetType).rawType; String packageName = getPackage(enclosingElement).getQualifiedName().toString(); String className = enclosingElement.getQualifiedName().toString().substring( packageName.length() + 1).replace(., $); ClassName bindingClassName = ClassName.get(packageName, className + _ViewBinding); boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); /参数:泛型、将要生成的类名称、是否Final修饰、是否View内部,是否Activity内部、是否Dialog内部; return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);首先判断了我们外部类Element是不是View是不是Activity是不是Dialog,进行了泛型判断处理,通过包名与类名得到我们将要创建的Java类的名称,有没有final修饰,然后通过以上判断的结果生成Builder对象返回。这样我们就创建了一个BindingSet.Builder对象,其中包含是不是View,activity,Dialog,将来要生成的Java类名,有没有final修饰,泛型信息。再次回到parseBindView()方法中看剩余部分:String name = simpleName.toString();TypeName type = TypeName.get(elementType);/判断是否添加了Nullableboolean required = isFieldRequired(element);/通过Id创建ViewBinding.Builder并setFieldBinding(fieldViewBinding)builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required);/ Add the type-erased version to the valid binding targets set.erasedTargetNames.add(enclosingElement);获取注解的element的name(上边例子中的btn)与type(上边例子中的Button),判断了是否添加了Nullable注解,通过这三个内容生成FieldViewBinding(与MethodViewBinding都继承了MemberViewBinding,封装一个最基础的BindView注解的element对象,)对象,调用builder的addField()方法将Id对象与FieldViewBinding对象传入,进入BindingSet的Buidler看一下:void addField(Id id, FieldViewBinding binding) getOrCreateViewBindings(id).setFieldBinding(binding);又调用了getOrCreateViewBindings()方法,跟:private ViewBinding.Builder getOrCreateViewBindings(Id id) ViewBinding.Builder viewId = viewIdMap.get(id); /创建针对当前id的ViewBinding.Builder; if (viewId = null) viewId = new ViewBinding.Builder(id); viewIdMap.put(id, viewId); return viewId;通过查看缓存中是否已经保存了当前id对应的ViewBinding.Builder,有返回,没有创建并缓存。在这里通过id创建了ViewBinding.Builder对象。回到addField()中,调用创建好的ViewBinding.Builder对象的setFieldBingding()方法将FieldViewBinding对象保存。这里的层次你得搞清楚了。FieldViewBinding保存在ViewBinding.Builder对象中,ViewBinding.Builder对象是在BindingSet.Builder对象调用addField()方法时创建的。回到parseBindView()方法,还剩一句代码,就是将父类的elementType保存到一开始的erasedTargetNames中。好,我们的parseBindView()方法就分析完了。同样,我们解析BindView注解逻辑也就分析完了,你弄明白了吗,不明白也没关系,我做一下总结:1. 首先就是一系列判断我们应用BindView注解的正确性;2. 然后获取id,获取外部类ElementType对应的BindingSet.Builder对象是否存在,存在判断当前id是否已经注解过。不存在,创建。创建的过程中获取了类是否为View、activity、Dialog,是否final,泛型,将生成的类名等。保存到builderMap中。3. 调用BindingSet.Builder对象的addField()方法,过程中创建了与之对应的FieldViewBinding对象、ViewBinding.Buidler对象,并将部分信息进行缓存。4. 将外部类ElementType保存到erasedTargetNames中。这就是解析ViewBind注解的整个过程。下边说一下个人的理解,如果一个类(如Activity)里边有ViewBind注解的控件,那么就会对应这个类生成一个BindingSet.Builder,每一个ViewBind注解的id会对应生成一个ViewBinding.Builder对象,一个类里边的所有生成的ViewBinding.Builder对象会根据id保存到BindingSet中的viewIdMap中。ViewBinding.Builder中保存了自己的FieldViewBinding对象。最终就是把生成的ViewBinding.Builder对象与外部类的ElementType对应着保存到了builderMap中,将外部类的ElementType保存到erasedTargetNames中。好了,关于ViewBind的解析就分析到这里。3.OnClick注解解析过程分析/ Process each annotation that corresponds to a listener.for (Class listener : LISTENERS) findAndParseListener(env, listener, builderMap, erasedTargetNames);下边看一下ButterKnife的事件处理流程,通过上边的代码可以看出来,所有的事件注解处理流程都是一样的,看一下for循环中方法,这里传入的是辅助对象env,当前要处理的事件注解类listener,还有就是builderMap,erasedTargetNames,我们进入findAndParseListener()方法,看一下:/* * 传入一种事件注解进行处理; * * param env * param annotationClass * param builderMap * param erasedTargetNames */private void findAndParseListener(RoundEnvironment env, Class annotationClass, Map builderMap, Set erasedTargetNames) /获取被某个事件注解的每一个element; for (Element element : env.getElementsAnnotatedWith(annotationClass) /google封装的方法验证element合法性; if (!SuperficialValidation.validateElement(element) continue; try /anotationClass是OnClickListener;element是被其注解的方法类型的element; parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames); catch (Exception e) StringWriter stackTrace = new StringWriter(); e.printStackTrace(new PrintWriter(stackTrace); error(element, Unable to generate view binder for %s.nn%s, annotationClass.getSimpleName(), stackTrace.toString(); 部分代码有注释,看一下关键代码parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);这个方法比较长,这里就不贴了,首先还是判断合法性,这部分代码不太重要。/反射方式获取id数组;int ids = (int) annotationValue.invoke(annotation);./省略部分合法行应用代码;/生成类里边重写父类的方法;如OnClick中的doClick();ListenerMethod method;/获取ListenerClass注解内部的ListenerMethod注解信息;ListenerMethod methods = listener.method();if (methods.length 1) throw new IllegalStateException(String.format(Multiple listener methods specified on %s., annotationClass.getSimpleName(); else if (methods.length = 1) /OnClick等都是一个; if (listener.callbacks() != ListenerClass.NONE.class) throw new IllegalStateException( String.format(Both method() and callback() defined on %s., annotationClass.getSimpleName(); method = methods0; else /callback处理;OnItemSelected、OnPageChange、OnTextChanged Method annotationCallback = annotationClass.getDeclaredMethod(callback); Enum callback = (Enum) annotationCallback.invoke(annotation); Field callbackField = callback.getDeclaringClass().getField((); method = callbackField.getAnnotation(ListenerMethod.class); if (method = null) throw new IllegalStateException( String.format(No %s defined on %ss %s.%s., ListenerMethod.class.getSimpleName(), annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(), (); 上边代码主要是获取了在定义事件注解时定义的方法信息,这里看一下我们的OnClick注解是怎么定义的:Target(METHOD)Retention(CLASS)List
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 行政管理自考本科专业发展与试题及答案
- 行政管理的评价标准试题及答案
- 2025农业银行贷款合同范本
- 行政管理的社会超级结构研究试题及答案
- 2025合作伙伴(公司)合作协议合同范本
- 2025年管理心理学考试注意事项试题及答案
- 现代管理学中业务流程再造试题及答案
- 行政管理学学术研究试题及答案
- 2025婚礼策划公司员工合同样本
- 2025家庭雇佣家政服务员合同
- 第四课:印巴战争
- 电气设备-开篇绪论汇编
- 武汉绿地中心项目技术管理策划书(48页)
- 婚无远虑必有财忧法商思维营销之婚姻篇74张幻灯片
- 红外图像处理技术课件
- 小学一年级人民币学具图片最新整理直接打印
- 投掷:原地投掷垒球
- 港口码头常用安全警示标志
- 密闭式周围静脉输液技术PPT课件
- 电梯快车调试方法
- 主要材料损耗率表
评论
0/150
提交评论