Android 注解工具ButterKnife源码分析

Java注解Annotation

参考 公共技术点之 Java 注解 Annotation

归纳总结一下:

作用

a. 标记,用于告诉编译器一些信息
@Override就是,如果没有正确重载方法,编译不过
b. 编译时动态处理,如动态生成代码
ButterKnife就是此类
c. 运行时动态处理,如得到注解信息
用的比较少,运行时可通过Method.getAnnotation方法得到运行时注解信息

Annotation分类

1 标准Annotation,Override, Deprecated, SuppressWarnings
标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning

2 元Annotation,@Retention, @Target, @Inherited, @Documented
元 Annotation 是指用来定义Annotation的Annotation,在后面 Annotation 自定义部分会详细介绍含义

  1. @Documented 是否会保存到 Javadoc 文档中
  2. @Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为CLASS,SOURCE大都为Mark Annotation,这类Annotation大都用来校验,比如Override, SuppressWarnings
  3. @Target 可以用来修饰哪些程序元素,如TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有
  4. @Inherited 是否可以被继承,默认为false

3 自定义Annotation
自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
这里是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation,后面在自定义 Annotation 时会具体介绍

@Documented
@Retention(RetentionPolicy.RUNTIME) //运行时注解信息
@Target(ElementType.METHOD) //只能修饰方法
@Inherited
public @interface MethodInfo {
    String author() default "trinea@gmail.com";

    String date();

    int version() default 1;
}
public class App {
    @MethodInfo(author = "trinea.cn+android@gmail.com", date = "2014/02/14", version = 2)
    public String getAppName() {
        return "trinea";
    }
}

Annotation解析

  1. @Retention为SOURCE,就没啥解析了,可以看到编译后的class文件都木有@Override注解
    //Annotations are to be discarded by the compiler.
    只是为了校验,class字节码文件中不存在该注解信息

  2. @Retention为CLASS
    //Annotations are to be recorded in the class file by the compiler . but need not be retained by the VM at run time. This is the default behavior.注解信息被记录到class文件中,但是在运行时被忽略。
    编译器在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的process方法去处理
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env)SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境process 函数返回值表示这组annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理

  3. @Retention为RUNTIME
    //Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
    如果可以看到运行时可以拿到注解消息

public class Person {
    public static void main(String[] args) {
        try {
            Class cls = Class.forName("App");
            for (Method method : cls.getMethods()) {
                MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); //必须是RUNTIME类型
                if (methodInfo != null) {
                    System.out.println("method name:" + method.getName());
                    System.out.println("method author:" + methodInfo.author());
                    System.out.println("method version:" + methodInfo.version());
                    System.out.println("method date:" + methodInfo.date());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

ButterKnife源码解析

简单用法

public class MainActivity extends AppCompatActivity {
    @Bind(R.id.imageview)
    ImageView imageview;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        imageview.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;
            }
        });
    }
    @OnClick(R.id.imageview)
    void onClickImage() {
        Toast.makeText(this, "on click", Toast.LENGTH_SHORT).show();
    }
}

实现原理

注解申明

Bind注解的申明,此时可以看到编译期间注解

@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  int[] value();
}

@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  int[] value() default { View.NO_ID };
}

ButterKnifeProcessor.process编译期间生成辅助class文件

由上面《Annotation解析》章节可以得知,@Retention为CLASS的解析必须继承自 AbstractProcessor 的类,然后调用他们的process方法去处理

public final class ButterKnifeProcessor extends AbstractProcessor {
    ..........
    @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();
      try {
        JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
        Writer writer = jfo.openWriter();
        writer.write(bindingClass.brewJava());
        writer.flush();
        writer.close();
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }

    return true;
  }
  ..........
}

所以只要添加了butterknife的注解信息,那么编译期间就会去调用ButterKnifeProcessor类的process方法,这个方法主要目的就是编译期间生成额外的几个class字节码文件,具体是怎么做到的,就细谈了,也看不懂,只需要知道结果

MainActivity.class

public class MainActivity
  extends AppCompatActivity
{
  @Bind({2131492969}) //编译期间,注解所以存在于class文件
  ImageView imageview;

  protected void onCreate(Bundle savedInstanceState)
  { //@Override是在SOURCE标注,所以class文件中没有
    super.onCreate(savedInstanceState);
    setContentView(2130968601);
    ButterKnife.bind(this);
    this.imageview.setOnTouchListener(new View.OnTouchListener()
    {
      public boolean onTouch(View v, MotionEvent event)
      {
        return false;
      }
    });
  }

  @OnClick({2131492969})
  void onClickImage()
  {
    Toast.makeText(this, "on click", 0).show();
  }
}

MainActivity$1.class
这个就不用说啦,匿名内部类,implements View.OnTouchListener

MainActivity$$ViewBinder.class
ButterKnifeProcessor类的process方法生成的,注意此时实现了ViewBinder接口, 关键bind方法
target,source都是上面我们的MainActivity对象,可以看到此时对imageview属性通过Finder对象进行了赋值,同时对imageview绑定了点击事件,是就是回调MainActivity的onClickImage方法

public class MainActivity$$ViewBinder<T extends demo.lbb.test.MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131492969, "field 'imageview' and method 'onClickImage'");
    target.imageview = finder.castView(view, 2131492969, "field 'imageview'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(
          android.view.View p0
        ) {
          target.onClickImage();
        }
      });
  }

  @Override public void unbind(T target) {
    target.imageview = null;
  }
}

MainActivity$$ViewBinder$1.class
这个当然也是上面的匿名内部类了,extends DebouncingOnClickListener

运行时

onCreate方法中bind方法必定是要调用的,否则就算编译就算生成了ViewBinder的字节码文件,但是运行时类加载器是不会去加载ViewBinder的,所以无效

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
}

ButterKnife的bind方法

  public static void bind(Activity target) {
    bind(target, target, Finder.ACTIVITY);//finder类型为Activity
  }
  static void bind(Object target, Object source, Finder finder) {
    Class<?> targetClass = target.getClass();
    try {
      if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
      ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass); //此时得到的viewBinder就是上面的MainActivity$$ViewBinder.class了
      if (viewBinder != null) {
        viewBinder.bind(finder, target, source);//哈,调用MainActivity$$ViewBinder.class的bind方法, 此时target,source都是MainActivity对象了
      }
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
    }
  }
  private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) //cls就是MainActivity.class
      throws IllegalAccessException, InstantiationException {
    ViewBinder<Object> viewBinder = BINDERS.get(cls); //BINDERS集合中查找是否之前加载过
    if (viewBinder != null) {
      if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
      return viewBinder;
    }
    String clsName = cls.getName(); //clsName:demo.lbb.test.MainActivity
    if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER;
    }
    try {
      Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX); //哈,此时加载clsName:demo.lbb.test.MainActivity$$ViewBinder.class
      //noinspection unchecked
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
      if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    BINDERS.put(cls, viewBinder); //保存在BINDERS集合中
    return viewBinder;
  }

最后来看看MainActivity$$ViewBinder中的调用Finder类的findRequiredView方法查找view的过程,最后是执行了((Activity) source).findViewById(id)

  public enum Finder {
    ........
    ACTIVITY {
      @Override protected View findView(Object source, int id) {
        return ((Activity) source).findViewById(id);
      }

      @Override public Context getContext(Object source) {
        return (Activity) source;
      }
    },
   public <T> T findRequiredView(Object source, int id, String who) {
      T view = findOptionalView(source, id, who);
      if (view == null) {
        String name = getContext(source).getResources().getResourceEntryName(id);
        throw new IllegalStateException("Required view '"
            + name
            + "' with ID "
            + id
            + " for "
            + who
            + " was not found. If this view is optional add '@Nullable' annotation.");
      }
      return view;
    }

    public <T> T findOptionalView(Object source, int id, String who) {
      View view = findView(source, id);
      return castView(view, id, who);
    }
    protected abstract View findView(Object source, int id);
    .......

总结

只是说了下大概的流程,最关键的其实还是ButterKnifeProcessor.process方法,通过这个方法编译期间生成了辅助的ViewBinder字节码文件,然后通过ButterKnife.bind(this);的执行,就会去调用findViewById和setOnClickListener等方法
ButterKnifeProcessor.process怎么生成的ViewBinder字节码文件,现阶段我还不需要去了解,以后有时间去研究吧

优点:
代码简洁,少了很多重复性代码

缺点:
为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间。
从上可以看到onCreate中的ButterKnife.bind(this); 同时是在main线程中执行了很多流程,需要一定的时间,界面显示onResume之后,所以多少会有点延迟,同时更多的占用了内存

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android Studio的ButterKnife是一个用于简化Android开发中视图绑定的开库。它可以帮助开发者通过注解方式快速地绑定视图资,减少findViewById的使用,提高开发效率。 使用ButterKnife,首先需要在项目的build.gradle文件中添加依赖: ```groovy dependencies { implementation 'com.jakewharton:butterknife:10.2.1' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1' } ``` 然后,在需要使用ButterKnife的Activity或Fragment中,使用`@BindView`注解来绑定视图资。 例如,在Activity中绑定一个TextView: ```java public class MainActivity extends AppCompatActivity { @BindView(R.id.textView) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); // 视图绑定 // 使用textView textView.setText("Hello ButterKnife!"); } } ``` 在Fragment中使用类似的方式绑定视图资。 需要注意的是,使用ButterKnife进行视图绑定时,必须在`setContentView()`之后调用`ButterKnife.bind(this)`来完成绑定。 除了`@BindView`注解外,ButterKnife还提供了其他注解,如`@OnClick`用于点击事件的绑定、`@Nullable`用于可为空的注解等。 值得一提的是,自从Android Studio 3.6版本起,Google推出了ViewBinding功能,它提供了类似ButterKnife的视图绑定功能,并且是官方支持的。如果使用最新版本的Android Studio,建议使用ViewBinding来代替ButterKnife

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值