源码分析相关面试题

Activity相关面试题

与XMPP相关面试题

与性能优化相关面试题

与登录相关面试题

与开发相关面试题

与人事相关面试题

本文配套视频:

注解框架实现原理,手写ButterKnife实现自己的注解框架

初级程序员使用别人的框架,中级程序员不仅会使用别人的框架还知道内部的实现原理,高级程序员则按需编写自己的框架。添加该模块的目的就是想提交大家的逼格,让大家养成一个动手编写“自主知识产权”框架的意识。

1. 编写 ButterKnife框架

业界比较出名的基于完全注解方式就可以进行 UI 绑定和事件绑定,无需 findViewById 和 setClickListener 等的IOC(Inverse Of Control 控制反转,就是将 UI 的初始化和事件绑定的“权利”交给框架来完成)框架有:

ButterKnife使用如下:

img

img

会出现如下代码:

  1. @BindView(R.id.button01)
  2. Button mButton01;
  3. @BindView(R.id.button02)
  4. Button mButton02;
  5. @BindView(R.id.button03)
  6. Button mButton03;
  7. @butterknife.OnClick({R.id.button01, R.id.button02, R.id.button03})
  8. public void onClick(View view) {
  9. switch (view.getId()) {
  10. case R.id.button01:
  11. Toast.makeText(this, "butterknife-button01", Toast.LENGTH_SHORT).show();
  12. break;
  13. case R.id.button02:
  14. Toast.makeText(this, "butterknife-button02", Toast.LENGTH_SHORT).show();
  15. break;
  16. case R.id.button03:
  17. Toast.makeText(this, "butterknife-button03", Toast.LENGTH_SHORT).show();
  18. break;
  19. }
  20. }

点击每个按钮会弹出响应的Toast,如下:

img

这个就是ButterKnife的用法。

接下来,我们开始编写自己的框架实现 findViewById 和 setOnClickListener 功能。

  1. 编写自定义注解类 ViewInject 和 Click;

ViewInject 注解类用于添加在 Filed 上。Click 注解类用于添加到 Method 上。

【文件】ViewInject.java

  1. /**
  2. * @Retention 用于声明该注解生效的生命周期,有三个枚举值可以选择<br>
  3. * 1. RetentionPolicy.SOURCE 注释只保留在源码上面,编译成class的时候自动被编译器抹除
  4. * 2. RetentionPolicy.CLASS 注释只保留到字节码上面,VM加载字节码时自动抹除
  5. * 3. RetentionPolicy.RUNTIME 注释永久保留,可以被VM加载时加载到内存中
  6. * 注意:由于我们的目的是想在VM运行时对Filed上的该注解进行反射操作,因此Retention值必须设置为RUNTIME
  7. *
  8. * @Target 用于指定该注解可以声明在哪些成员上面,常见的值有FIELD和Method,
  9. 由于我们的当前注解类是想声明在Filed上面
  10. * 因此这里设置为ElementType.FIELD。
  11. * 注意:如果@Target值不设置,则默认可以添加到任何元素上,不推荐这么写。
  12. *
  13. * @interface 是声明注解类的组合关键字。
  14. */
  15. @Target({java.lang.annotation.ElementType.FIELD})
  16. @Retention(RetentionPolicy.RUNTIME)
  17. public @interface ViewInject {
  18. public abstract int value();
  19. }

【文件】Click.java

  1. @Target({java.lang.annotation.ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface OnClick
  4. {
  5. public abstract int[] value();
  6. }

编写核心方法 ViewUtilsTest.inject(Ativity);

  1. public class ViewUtilsTest {
  2. public static void inject(final Activity activity)
  3. {
  4. /**
  5. * 通过字节码获取activity类中所有的字段,在获取Field的时候一定要使用
  6. * getDeclaredFields(),
  7. * 因为只有该方法才能获取到任何权限修饰的Filed,包括私有的。
  8. */
  9. Class clazz = activity.getClass();
  10. Field[] declaredFields = clazz.getDeclaredFields();
  11. //一个Activity中可能有多个Field,因此遍历。
  12. for (int i = 0; i < declaredFields.length; i++) {
  13. Field field = declaredFields[i];
  14. //设置为可访问,暴力反射,就算是私有的也能访问到
  15. field.setAccessible(true);
  16. //获取到字段上面的注解对象
  17. ViewInject annotation = (ViewInject)field.getAnnotation(ViewInject.class);
  18. //一定对annotation是否等于null进行判断,因为并不是所有Filed上都有我们想要的注解
  19. if (annotation == null)
  20. {
  21. continue;
  22. }
  23. //获取注解中的值
  24. int id = annotation.value();
  25. //获取控件
  26. View view = activity.findViewById(id);
  27. try
  28. {
  29. //将该控件设置给field对象
  30. field.set(activity, view);
  31. } catch (IllegalArgumentException e) {
  32. e.printStackTrace();
  33. } catch (IllegalAccessException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. //获取所有的方法(私有方法也可以获取到)
  38. Method[] declaredMethods = clazz.getDeclaredMethods();
  39. for (int i = 0; i < declaredMethods.length; i++) {
  40. final Method method = declaredMethods[i];
  41. //获取方法上面的注解
  42. OnClick annotation = (OnClick)method.getAnnotation(OnClick.class);
  43. if (annotation == null) {
  44. //如果该方法上没有注解,循环下一个
  45. continue;
  46. }
  47. //获取注解中的数据,因为可以给多个button绑定点击事件,因此定义注解类时使用的是int[]作为数据类型。
  48. int[] value = annotation.value();
  49. for (int j = 0; j < value.length; j++) {
  50. int id = value[j];
  51. final View button = activity.findViewById(id);
  52. button.setOnClickListener(new View.OnClickListener()
  53. {
  54. public void onClick(View v)
  55. {
  56. try
  57. {
  58. //反射调用用户指定的方法
  59. method.invoke(activity,button);
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. });
  65. }
  66. }
  67. }
  68. }

咱们自己的注解框架就实现好了,效果如下:

img

  • 欢迎关注微信公众号,长期推荐技术文章和技术视频

微信公众号名称:Android干货程序员

img