Butterknife原理解析
来源:互联网 发布:李宗仁故居风水数据 编辑:程序博客网 时间:2024/06/08 19:39
转载请标明出处:【顾林海的博客】
前言
Butterknife是一个专注于Android系统的View注入框架,可以简化代码,比如findViewById、事件监听、资源绑定等,同时该框架使用了编译时注解,可能大家一听到编译时注解就认为这种方式会影响性能,其实编译时注解并不会影响应用的性能,这是因为编译时注解是在代码编译过程中对注解进行处理,生成代码,这些代码在运行时调用,除了编译时注解,还有一个是运行时注解,它是在运行过程中,通过反射获取相关类、方法、参数等信息,因此运行时注解会有性能问题。
原理解析
平时使用Butterknife时,需要调用Butterknife的bind方法,Butterknife提供以下几种bind方法:
上面六种方法最终都会调用createBinding方法,这里以bind(Activity)代码为例:
@NonNull @UiThreadpublic static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView);}
方法中通过传入的Activity获取DecorView,DecorView是整个View树的顶层View,内部包含标题栏和ContentView,而ContentView内部就是我们定义的视图View,拿到DecorView后调用用createBinding方法并把目标Activity和DecorView传入过去。
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); }}
方法中获取目标Class,并通过findBindingConstructorForClass方法获取构造函数,findBindingConstructorForClass方法代码如下:
@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); 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 { 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); } BINDINGS.put(cls, bindingCtor); return bindingCtor;}
BINDINGS是一个Map,用于缓存,方便下次使用时之间从集合中取出,如果集合中没有,会加载当前的class的名字加上_ViewBinding,比如MainActivity_ViewBinding,加载并获取该class,在获取它的两个参数的构造函数,最后将加载的class进行缓存存入BINDINGS集合中。之后通过createBinding方法中的newInstance进行实例化。
从上面Butterknife执行的绑定方法就可以知道先去加载classname+_ViewBinding的类,并进行实例化,但这个类我们并没有编写,是自动生成的,也就是编译时生成,编译时注解时需要AbstractProcessor这个类来实现,需要重写它的process方法,比如下面:
public class TestProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // TODO Auto-generated method stub return false; } } }}
在process方法中可以扫描和处理注解的代码,并会生成相关的Java文件,查看Butterknife中继承了AbstractProcessor的类ButterKnifeProcessor中的process方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false;}
findAndParseTargts是用于处理@BindViewXX注解的方法,内部有多个循环代码,这些for循环的作用是对注解进行处理。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); // Process each @BindAnim element. for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceAnimation(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindAnim.class, e); } } // Process each @BindArray element. for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceArray(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } } // Process each @BindBitmap element. for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBitmap(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBitmap.class, e); } } // Process each @BindBool element. for (Element element : env.getElementsAnnotatedWith(BindBool.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBool(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBool.class, e); } } // Process each @BindColor element. for (Element element : env.getElementsAnnotatedWith(BindColor.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceColor(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindColor.class, e); } } // Process each @BindDimen element. for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceDimen(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDimen.class, e); } } // Process each @BindDrawable element. for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceDrawable(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDrawable.class, e); } } // Process each @BindFloat element. for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceFloat(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindFloat.class, e); } } // Process each @BindFont element. for (Element element : env.getElementsAnnotatedWith(BindFont.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceFont(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindFont.class, e); } } // Process each @BindInt element. for (Element element : env.getElementsAnnotatedWith(BindInt.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceInt(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindInt.class, e); } } // Process each @BindString element. for (Element element : env.getElementsAnnotatedWith(BindString.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceString(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindString.class, e); } } // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { // we don't 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); } } // Process each @BindViews element. for (Element element : env.getElementsAnnotatedWith(BindViews.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindViews(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindViews.class, e); } } // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } // Associate superclass binders with their subclass binders. This is a queue-based tree walk // which starts at the roots (superclasses) and walks to the leafs (subclasses). Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast(entry); } } } return bindingMap;}
方法代码很多,主要是对下面注解的处理:
处理完毕后会通过JavaFile javaFile = binding.brewJava(sdk, debuggable);生成Java文件。
接着看生成的文件比如我们的MainActivity_ViewBinding文件,路径在build/generated/source/apt/debug/packgename/下:
public class MainActivity extends AppCompatActivity { @BindView(R.id.btn_test) Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); mButton.setText("测试"); } @OnClick(R.id.btn_test) public void clickButton() { Toast.makeText(this, "test", Toast.LENGTH_SHORT).show(); }}public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131427415; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(final MainActivity target, View source) { this.target = target; View view; view = Utils.findRequiredView(source, R.id.btn_test, "field 'mButton' and method 'clickButton'"); target.mButton = Utils.castView(view, R.id.btn_test, "field 'mButton'", Button.class); view2131427415 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.clickButton(); } }); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.mButton = null; view2131427415.setOnClickListener(null); view2131427415 = null; }}public abstract class DebouncingOnClickListener implements View.OnClickListener { static boolean enabled = true; private static final Runnable ENABLE_AGAIN = new Runnable() { @Override public void run() { enabled = true; } }; @Override public final void onClick(View v) { if (enabled) { enabled = false; v.post(ENABLE_AGAIN); doClick(v); } } public abstract void doClick(View v);}
在上面通过ButterKnife的bind方法会通过两个参数的构造函数进行实例化,在构造函数中,可以看出最终还是会调用findViewById方法,并对view进行点击事件的监听,DebouncingOnClickListener是View.OnclickListener的子类,用于防止一定时间内对View的多次点击,在onClick方法中执行抽象方法doClick,在MainActivity_ViewBinding的构造函数中,可以看到为Button的点击时会调用MainActivity的clickButton方法,也就是在MainActivity中通过注解@OnClick定义的方法。
- 解析ButterKnife实现原理
- 解析ButterKnife实现原理
- ButterKnife原理解析
- Butterknife原理解析
- 高效懒人工具ButterKnife原理解析
- Android-自己动手写ButterKnife与原理解析
- Butterknife原理
- ButterKnife解析
- butterKnife原理学习
- ButterKnife框架原理
- ButterKnife 的实现原理
- ButterKnife框架原理解剖
- ButterKnife框架原理
- ButterKnife框架原理
- ButterKnife的原理
- ButterKnife源码解析
- ButterKnife完全解析
- Butterknife全方位解析
- 手把手教你入门使用 tf-slim 库 | 回顾
- 深度学习中 GPU 和显存分析
- C 运算符优先级
- C++ break 和 cintinue
- Amoeba实现Mysql主从复制读写分离
- Butterknife原理解析
- 关于Qt中LOGO与窗口图标显示问题的总结
- SSD固态硬盘优化方案,让新买的SSD速度不再慢
- VC/MFC 从WebBrower 中获取 HTML 和文本
- Dihedral Group
- CSDN-markdown编辑器
- [操作系统] I/O软件原理
- Java 字符串去中文(数字)
- Linux下去掉^M的方法