前言部分
本文主要是分为两个内容记录,一个是介绍ButterKnife的实现原理,不过我只是简单的以一个例子进行,比如我们常用的代替findviewbyid(int id)方法的注解;另一个是介绍一下本地简单的实现,如果理解了ButterKnife的原理自己写也没问题的了。
我记得ButterKnife的前期版本是用的运行时的注解,后来不知道在哪个版本改成编译期注解了,不过这也是顺理成章的,毕竟相对于运行时的注解方案,现在的方案更有优势,不需要通过大量的反射去调用方法,这对性能上是很有优势的(但是也有人说,现在的Java反射没有以前那么耗费性能了)。
内容部分
第一部分先介绍一下我写的Demo
我先介绍一下我写的一个简单的注解框架了,写的很简单只是简单的实现了ButterKnife的findviewbyid和onclick两个注解的功能
注解的主要步骤,如下:
-
首先定一个注解,这个就是你写在成员变量上的,里面只有一个int类型的属性。
-
@Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); }
-
然后定一个注解处理器,我们主要是通过实现AbstractProcessor类来完成对注解的处理。主要实现下面的三个方法即可。
//jdk版本,用最新的了 @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } //支持的都是什么注解 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(BindView.class.getCanonicalName()); types.add(ClickView.class.getCanonicalName()); return types; } //所有的注解都会在这里汇集,我们在这里来做处理,比如,我们在这里生成类。里面内容比较多就不贴出来了。 @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { }
-
注解生成完了,我们就可以使用了啊,使用之前我们先写一些我们需要的api,下面就介绍一个@BindView()注解吧
public class MainActivity extends AppCompatActivity { //我们这么使用,我们声明了成员变量,下面通过初始化上面我们注解生成类的构造函数,来给成员变量赋值。 @BindView(R.id.tv) TextView tv; //然后再类里面 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // AnnotationUtils.getClassInfo("com.dongfang.eventbusdemo.MainActivity"); DFButterKnife.inject(this); } }
我们这里调用了inject()方法后,就可以初始化注解生成的类了,然后成员就会得到赋值并使用了。我们在到inject()里面看一下,代码不多我直接贴出来了,如下:
public class DFButterKnife { private static final String SUFFIX = "$$" + ViewInject.class.getSimpleName(); public static void inject(Activity activity) { inject(activity, activity); } public static void inject(Object host, Object root) { Class<?> clazz = host.getClass(); //拼接类的全路径, String proxyClassFullName = clazz.getName() + SUFFIX; Class<?> proxyClazz = null; try { proxyClazz = Class.forName(proxyClassFullName); //通过newInstance生成实例,强转,调用代理类的inject方法 ViewInject viewInject = (ViewInject) proxyClazz.newInstance(); //调用生成类里面的inject方法,进行findView viewInject.inject(host, root); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
上面的核心内容主要是通过传入的class来拼接出,注解预先生成的class,然后通过反射来生成类的实例,在通过实例来调用inject()方法。我们去看一下生成类的这个方法。如下:
public class MainActivity$$ViewInject implements ViewInject<com.dongfang.eventbusdemo.MainActivity> { public com.dongfang.eventbusdemo.MainActivity tage; public View viewTage; @Override public void inject(com.dongfang.eventbusdemo.MainActivity host, Object source) { tage = host; if (source instanceof android.app.Activity) { host.tv = (android.widget.TextView) (((android.app.Activity) source).findViewById(2131165313)); } else { host.tv = (android.widget.TextView) (((android.view.View) source).findViewById(2131165313)); } //完成成员变量注解 if (source instanceof android.app.Activity) { viewTage = (((android.app.Activity) source).findViewById(2131165217)); } else { viewTage = (((android.view.View) source).findViewById(2131165217)); } viewTage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { tage.onClick(); } }); } }
这个和ButterKnife的有一点点不同啦,初始化是在inject()方法中,而ButterKnife是在构造成完成这个操作。到了这步骤就算是完成了,但是十分简陋的一个demo。达不到使用的标准。
到这这里我的demo就算是完成了,其实现在注解能做的东西有很多,因为注解大大的减少了类之间的耦合。
第二部分介绍一下源码(简单的一带而过)
我们在项目中使用 @BindView(R.id.ll_main_tab) 注解,然后项目在编译的过程中生成添加了注解的的类的对应的一个 类名为 “类名_ViewBinding” 的类,里面存储我们需要的注解的内容,比如为view中的成员变量赋值等。
这是使用注解的前提,因为注解生成了这个类,我们其实在调用方法或者使用变量的时候是不需要反射的
上面所说的成员变量的赋值,都是在这个生成的类的构造中完成。下面我们可以看到,通过ButterKnife.bind(this);调用生成的类的构造函数。
这里说明一下,这只是最简陋的原理了,其实ButterKnife中做的更加详细和高效,比如,我们在运行项目后,每个注解后的类在打开后,都会存储到一个静态的map中,就是其实在页面打开一次后,就不会重复的去做调用_ViewBinding的构造函数,而是在记录的集合中去取,提高运行效率。
下面开始我们的源码跟读,首先我们找到一个入口,如下:
//点一下
ButterKnife.bind(this);
//到这里了
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
//拿到了rootview
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
好吧大家都是这么开始的,下面开始进入到有内容的方法了,
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
//找一下Constructor,点进去看一下
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) {
//省略。。。
}
}
下面在进入到findBindingConstructorForClass()看一下吧。这个方法中主要是对注解生成的类进行了实例化,然后就并且存入到上面的map中,提高以后再次查找的效率。
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//这里就是我们生成的类的名称了,就是在我们传入的类的后面➕_ViewBinding就是类名。
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked。这里调用了我们生成的类的构造函数,里面就进行了我们上面的findRequiredView()方法
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
//省略。。。
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
上面的步骤完成基本上你的activity中的成员变量已经进行了赋值,下面你就可以愉快的使用了。
结尾
上面就是对自己学习过程的记录吧,感觉写的很粗糙,其实注解这一块是可以详细的来展开讲的,但是我发现了一个很不错的文章,所以这里推荐出来。注解分析
写的很浅显易懂,读起来不安么枯燥。