




已阅读5页,还剩8页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Butterknife用法我相信学过Android开发应该基本上都用过Butterknife吧,就算没用过也听说过吧?毕竟是大名鼎鼎的Jake Wharton出品的东西,如果没用过,就分享下面这篇Java基础之注解annotation里面虽然是讲的Annotation,但是例子就是用注解加反射实现的低级的Butterknife。哈哈!用法里面大概也说了下。前言从 jdk5开始,Java增加了对元数据的支持,也就是Annotation,Annotation其实就是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理。当然刚刚说了,Annotation只是一种标记,所以要是在代码里面不用这些标记也是能完成相应的工作的,只是有时候用注解能简化很多代码,看起来非常的简洁。基本的AnnotationOverride限定重写父类方法Deprecated标示已过时SuppressWarning抑制编译器警告SafeVarargs这货与Java7里面的堆污染有关,具体想了解的,传送到这里JDK的元AnnotationJDK除了提供上述的几种基本的Annotation外,还提供了几种Annotation,用于修饰其他的Annotation定义Retention 这个是决定你Annotation存活的时间的,它包含一个RetationPolicy的value成员变量,用于指定它所修饰的Annotation保留时间,一般有:Retationpolicy.CLASS:编译器将把Annotation记录在Class文件中,不过当java程序执行的时候,JVM将抛弃它。Retationpolicy.SOURCE : Annotation只保留在原代码中,当编译器编译的时候就会抛弃它。Retationpolicy.RUNTIME : 在Retationpolicy.CLASS的基础上,JVM执行的时候也不会抛弃它,所以我们一般在程序中可以通过反射来获得这个注解,然后进行处理。Target 这个注解一般用来指定被修饰的Annotation修饰哪些元素,这个注解也包含一个value变量:ElementType.ANNOTATION_TYPE : 指定该Annotation只能修饰Annotation。ElementType.CONSTRUCTOR: 指定只能修饰构造器。ElementType.FIELD: 指定只能成员变量。ElementType.LOCAL_VARIABLE: 指定只能修饰局部变量。ElementType.METHOD: 指定只能修饰方法。ElementType.PACKAGE: 指定只能修饰包定义。ElementType.PARAMETER: 指定只能修饰参数。ElementType.TYPE: 指定可以修饰类,接口,枚举定义。Document 这个注解修饰的Annotation类可以被javadoc工具提取成文档Inherited 被他修饰的Annotation具有继承性自定义Annotation上面讲了一些jdk自带的Annotation,那么我们现在就可以用这些jdk自带的Annotation来实现一些我们想要的功能。由于最近在看butterknife的源码,那么我们就一步一步地模仿butterknife的实现吧。首先先讲一下的用法吧:ContentView(R.layout.activity_main)public class MainActivity extends AppCompatActivity ViewInject(R.id.text_view) private TextView textView; OnClick(R.id.text_view) private void onClick(View view) textView.setText(我是click后的textview); Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); ViewInjectUtils.inject(this); textView.setText(我是click前的textview); 上面是这篇文章最后的实现,自从用了注解后,妈妈再也不用担心我一遍一遍地写findViewById和setOnClickListener了。编码首先我们要先定义我们要用的接口,哦不!是注解。注意和接口不一样哦!这里我们先实现ContentView的功能,再来实现ViewInject和OnClickpackage com.qhung.annotation.ioc.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/* * Created by qhung on 2016/5/3. */Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)public interface ContentView int value();啊,这里的Target和Retention大家应该都清楚是什么意思了哈,定义注解的方式就是interface 和接口的定义方式就少一个哦,不要搞混了。里面有一个变量value,就是我们使用的时候ContentView(R.layout.activity_main)指定的R.layout.activity_main布局文件,旨在自动注入布局文件。因为这里只有一个变量value,所以不用写成name=value的形式。然后大家还记得我们在Activity里面调用的ViewInjectUtils.inject(this);?哈哈!其实我们处理注解的逻辑全在这个里面,那么我们就看看这个里面又是一番什么天地:package com.qhung.annotation.ioc.annotation;import android.app.Activity;import android.view.View;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/* * * Created by qhung on 2016/5/3. */public class ViewInjectUtils public static void inject(Activity activity) injectContentView(activity); private static void injectContentView(Activity activity) Class clazz = activity.getClass(); ContentView contentView = clazz.getAnnotation(ContentView.class); if (contentView != null) /如果这个activity上面存在这个注解的话,就取出这个注解对应的value值,其实就是前面说的布局文件。 int layoutId = contentView.value(); try Method setViewMethod = clazz.getMethod(setContentView, int.class); setViewMethod.invoke(activity, layoutId); catch (Exception e) e.printStackTrace(); 原来ViewInjectUtils.inject(this)里面调用了injectContentView(activity),在injectContentView(activity)里面,我们拿到了Activity的Class,然后在第23行,我们拿到了这个class的ContentView注解,然后再通过反射调用setContentView方法,完成注入。其实这里是在运行的时候完成的,所以我们在定义注解的时候,设置为Retention为RUNTIME。好了,这个功能到这里就完了。下面继续完成第二个功能:ViewInject同样先贴上ViewInject类:package com.qhung.annotation.ioc.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/* * Created by qhung on 2016/5/3. */Target(ElementType.FIELD)Retention(RetentionPolicy.RUNTIME)public interface ViewInject int value();其实这个定义和上面ContentView的定义一样,然后我们再看看我们是怎么处理的: public static void inject(Activity activity) injectContentView(activity); injectView(activity); private static void injectView(Activity activity) Class clazz = activity.getClass(); /获得activity的所有成员变量 Field fields = clazz.getDeclaredFields(); for (Field field : fields) /获得每个成员变量上面的ViewInject注解,没有的话,就会返回null ViewInject viewInject = field.getAnnotation(ViewInject.class); if (viewInject != null) int viewId = viewInject.value(); View view = activity.findViewById(viewId); try field.setAccessible(true); field.set(activity, view); catch (IllegalAccessException e) e.printStackTrace(); 获得所有属性,然后遍历带有ViewInject注解的属性,然后拿到ViewInject注解的View的id,然后通过activity.findViewById(viewId);获得这个View。然后设置给field。最后一个功能:EventInject这个功能稍微麻烦一点,因为我们平时设置的点击时间是用setiOnClickListener();然后View.OnClickListener是一个接口,不能用反射来获得他的实例,那么怎么办呢?其实我们这里可以巧妙地用动态代理来完成,当view被点击的时候,我们通过动态代理来调用onclick就行。下面是代码:Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)public interface OnClick int value();public class ViewInjectUtils public static void inject(Activity activity) injectContentView(activity); injectView(activity); injectEvent(activity); private static void injectEvent(final Activity activity) Class clazz = activity.getClass(); Method methods = clazz.getDeclaredMethods(); for (final Method method2 : methods) OnClick click = method2.getAnnotation(OnClick.class); if (click != null) int viewId = click.value(); method2.setAccessible(true); Object listener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(), new ClassView.OnClickListener.class, new InvocationHandler() Override public Object invoke(Object proxy, Method method, Object args) throws Throwable return method2.invoke(activity, args); ); try for (int id : viewId) View v = activity.findViewById(id); Method setClickListener = v.getClass().getMethod(setOnClickListener, View.OnClickListener.class); setClickListener.invoke(v, listener); catch (Exception e) e.printStackTrace(); listener是一个代理对象,然后我们调用setOnClickListener的时候,把这个代理对象传进去。当发生点击的时候,就会invoke方法,这时我们就可以调用带有onClick注解的method方法了。下面看一下运行结果:现在我们就完成了类似butterknife的功能了,不过butterknife最新版本不是通过反射来完成的,因为反射会有性能问题,虽然现在对性能影响不大,但是作为程序员,能优化的就要尽量优化,不能只停留在能用的基础上面。Butterknife原理讲到butterknife的原理。这里不得不提一下一般这种注入框架都是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那么有没有一种方法能解决这种性能的损耗呢? 没错,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)编译时解析技术。APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。其实这种方式的好处是我们不用再一遍一遍地写findViewById和onClick了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。源码解析上面讲了那么多,其实都不如直接解析源码来得直接,下面我们就一步一步来探究大神怎样实现Butterknife的吧。拿到源码的第一步是从我们调用的地方来突破,那我们就来看看程序里面是怎样调用它的呢? Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.setDebug(true); ButterKnife.bind(this); / Contrived code to use the bound fields. title.setText(Butter Knife); subtitle.setText(Field and method binding for Android views.); footer.setText(by Daxia); hello.setText(Say Hello); adapter = new SimpleAdapter(this); listOfThings.setAdapter(adapter); 上面是github上给的例子,我们直接就从 ButterKnife.bind(this)入手吧,点进来看看: public static Unbinder bind(NonNull Activity target) return bind(target, target, Finder.ACTIVITY); 咦?我再点: static Unbinder bind(NonNull Object target, NonNull Object source, NonNull Finder finder) Class targetClass = target.getClass(); try ViewBinder viewBinder = findViewBinderForClass(targetClass); return viewBinder.bind(finder, target, source); catch (Exception e) throw new RuntimeException(Unable to bind views for + targetClass.getName(), e); 好吧,bind方法主要就是拿到我们绑定的Activity的Class,然后找到这个Class的ViewBinder,最后调用ViewBinder的bind()方法,那么问题来了,ViewBinder是个什么鬼?我们打开findViewBinderForClass()方法。 NonNull private static ViewBinder findViewBinderForClass(Class cls) throws IllegalAccessException, InstantiationException ViewBinder viewBinder = BINDERS.get(cls); if (viewBinder != null) return viewBinder; String clsName = cls.getName(); try Class viewBindingClass = Class.forName(clsName + $ViewBinder); viewBinder = (ViewBinder) viewBindingClass.newInstance(); catch (ClassNotFndException e) viewBinder = findViewBinderForClass(cls.getSuperclass(); BINDERS.put(cls, viewBinder); return viewBinder; 这里我去掉了一些Log信息,保留了关键代码,上面的BINDERS是一个保存了Class为key,Class$ViewBinder为Value的一个LinkedHashMap,主要是做一下缓存,提高下次再来bind的性能。在第10行的时候,clsName 是我们传入要绑定的Activity类名,这里相当于拿到了Activity$ViewBinder这个东西,这个类又是什么玩意儿?其实从类名可以看出来,相当于Activity的一个内部类,这时候我们就要问了,我们在用的时候没有声明这个类啊?从哪里来的? 不要方,其实它就是我们在之前讲原理的时候说到的AbstractProcessor在编译的时候生成的一个类,我们后面再来看它,现在我们继续往下面分析。在第11行就用反射反射了一个viewBinder 实例出来。刚刚说了,这个方法里面用linkhashMap做了下缓存,所以在15行的时候,就把刚刚反射的viewBinder作为value,Class作为key加入这个LinkedHashMap,下次再bind这个类的时候,就直接在第4行的时候取出来用,提升性能。现在返回刚刚的bind方法,我们拿到了这个Activity的viewBinder,然后调用它的bind方法。咦?这就完了?我们再点进viewBinder的bind方法看看。public interface ViewBinder Unbinder bind(Finder finder, T target, Object source);什么,接口?什么鬼?刚刚不是new了一个viewBinder出来么?然后这里就调用了这个viewBinder的bind方法, 不行,我要看一下bind到底是什么鬼!上面说了,Butterknife用了APT技术,那么这里的viewBinder应该就是编译的时候生成的,那么我们就反编译下apk。看看到底生成了什么代码:下面我们就先用一个简单的绑定TextView的例子,然后反编译出来看看:public class MainActivity extends AppCompatActivity Bind(R.id.text_view) TextView textView; OnClick(R.id.text_view) void onClick(View view) textView.setText(我被click了); Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); textView.setText(我还没有被click); 源代码就这行几行,然后反编译看看:源代码就多了一个类,MainActivity$ViewBinder,打开看看:public class MainActivity$ViewBinder implements ButterKnife.ViewBinder public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject) View localView = (View)paramFinder.findRequiredView(paramObject, 2131492944, field textView and method onClick); paramT.textView = (TextView)paramFinder.castView(localView, 2131492944, field textView); localView.setOnClickListener(new DebouncingOnClickListener() public void doClick(View paramAnonymousView) paramT.onClick(paramAnonymousView); ); public void unbind(T paramT) paramT.textView = null; 还记得刚刚说的,反射了一个Class$ViewBinder么?看这里的类名。现在应该懂了吧?它刚好也是实现了ButterKnife.ViewBinder接口,我们说了,在bind方法中,最后调用了ViewBinder的bind方法,先说下几个参数paramFinder其实就是一个Finder,因为我们可以在Activity中使用butterknife,也可以在Fragment和Adapter等中使用butterknife,那么在不同的地方使用butterknife,这个Finder也就不同。在Activity中,其实源码 就是这样子的: ACTIVITY Override protected View findView(Object source, int id) return (Activity) source).findViewById(id); Override public Context getContext(Object source) return (Activity) source; 有没有很熟悉?其实还是用的findViewById,那么在Dialog和Fragment中,根据不同的地方,实现的方式不同。这里的paramT和paramObject都是我们要绑定的Activity类,通过代码可以跟踪到。返回上面的ViewBinder代码,首先调用了Finder的findRequiredView方法,其实这个方法最后经过处理就是调用了findView方法,拿到相应的view,然后再赋值给paramT.textView,刚说了paramT就是那个要绑定的Activity,现在懂了吧?这里通过 paramT.textView 这样的调用方式,说明了Activity中不能把TextView设置为private,不然会报错,其实这里可以用反射来拿到textView的,这里大概也是为了性能着想吧。最后setOnClickListener,DebouncingOnClickListener这个Listener其实也是实现了View.OnClickListener 方法,然后在OnClick里面调用了doClick方法。流程大概跟踪了一遍。现在还留下最后一块了:Butterknife到底是怎样在编译的时候生成代码的?我们来看一下它的ButterKnifeProcessor类:Init方法: Override public synchronized void init(ProcessingEnvironment env) super.init(env); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。Types是用来处理TypeMirror的工具类,Filer用来创建生成辅助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运行的时候,会扫描所有的Java源文件,然后每一个Java源文件的每一个部分都是一个Element,比如一个包、类或者方法。 Override public Set getSupportedAnnotationTypes() Set types = new LinkedHashSet(); types.add(BindArray.class.getCanonicalName(); types.add(BindBitmap.class.getCanonicalName(); types.add(BindBool.class.getCanonicalName(); types.add(BindColor.class.getCanonicalName(); types.add(BindDimen.class.getCanonicalName(); types.add(BindDrawable.class.getCanonicalName(); types.add(BindInt.class.getCanonicalName(); types.add(BindString.class.getCanonicalName(); types.add(BindView.class.getCanonicalName(); types.add(BindViews.class.getCanonicalName(); for (Class listener : LISTENERS) types.add(listener.getCanonicalName(); return types; getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。我们可以看到,在源代码里面,作者一个一个地把Class文件加到那个LinkedHashSet里面,然后再把LISTENERS也全部加进去。其实整个类最重要的是process方法: Override public boolean process(Set elements, RoundEnvironment env) Map targetClassMap = findAndParseTargets(env); for (Map.Entry entry : targetClassMap.entrySet() TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); try bindingClass.brewJava().writeTo(filer); catch (IOException e) error(typeElement, Unable to write view binder for type %s: %s, typeElement, e.getMessage(); return true; 这个方法的作用主要是扫描、评估和处理我们程序中的注解,然后生成Java文件。也就是前面说的ViewBinder。首先一进这个函数就调用了findAndParseTargets方法,我们就去看看findAndParseTargets方法到底做了什么: private Map findAndParseTargets(RoundEnvironment env) Map targetClassMap = new LinkedHashMap(); Set erasedTargetNames = new LinkedHashSet(); / Process each BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class) if (!SuperficialValidation.validateElement(element) continue; try parseBindView(element, targetClassMap, erasedTargetNames); catch (Exception e) logParsingError(element, BindView.class, e); Observable.from(topLevelClasses) .flatMap(new Func1BindingClass, Observable() Override public Observable call(BindingClass topLevelClass) if (topLevelClass.hasViewBindings() / It has an unbinder class and it will also be the highest unbinder class for all / descendants. topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName(); else / No unbinder class, so null it out so we know we can just return the NOP unbinder. topLevelClass.setUnbinderClassName(null); / Recursively set up parent unbinding relationships on all its descendants. return ButterKnifeProcessor.this.setParentUnbindingRelationships( topLevelClass.getDescendants(); ) .toCompletable() .await(); return targetClassMap; 这里代码炒鸡多,我就不全部贴出来了,只贴出来一部分,这个方法最后还用了rxjava的样子。这个方法的主要的流程如下:扫描所有具有注解的类,然后根据这些类的信息生成BindingClass,最后生成以TypeElement为键,BindingClass为值的键值对。循环遍历这个键值对,根据TypeElement和BindingClass里面的信息生成对应的java类。例如AnnotationActivity生成的类即为Cliass$ViewBinder类。因为我们之前用的例子是绑定的一个View,所以我们就只贴了解析View的代码。好吧,这里遍历了所有带有BindView的Element,然后对每一个Element进行解析,也就进入了parseBindView这个方法中:private void parseBindView(Element element, Map targetClassMap, Set erasedTargetNames) 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 = el
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年初级人力资源专员面试指南及模拟题集
- 2025年年中国食品饮料项目建议书
- 抢救车物品培训课件
- 抢救药管理课件
- 2025年工业VOC治理系统项目合作计划书
- 2025-2026学年北师大版(2024)小学数学三年级上册《看一看(二)》教学设计
- 2025年子宫收缩药项目合作计划书
- 2025年散热器用复合铝箔项目发展计划
- 2025年钻孔应变仪项目发展计划
- 2025年系列催化裂化催化剂项目建议书
- 2025成人高考政治试题及答案专升本
- 2025秋冀人版(2024)科学二年级上册教学计划、教学设计(附目录)
- 板材样品销售方案(3篇)
- 污水处理厂设备更新项目可行性研究报告
- 1.1.1观察周边环境中的生物 课件 人教版生物七年级上册
- 2025反洗钱知识题库及答案
- 110kV变电站通信系统施工方案与技术要求
- (高清版)DBJ∕T 13-91-2025 《福建省房屋市政工程安全风险分级管控与隐患排查治理标准》
- 多系统联合仿真平台在燃气轮机设计与开发中的应用
- 中班健康:变质食物不能吃
- 工程造价专业成长路径与技能提升
评论
0/150
提交评论