写在博客前面的有用的废话:为什么会写这篇博客,很多人会问,ButterKnife很简单啊,我会用啊,你写这有毛用,我用第三方的贼6,谁还自己写这玩意儿啊。。。。是啊,第三方的东西很nb,但是如果我们只停留在简单的会用层次上,我们永远只能Ctrl + C + V 写代码,如此一来我们就很难成为那些提供那些造轮子的人。所以我建议大家不止要把轮子咕噜起来,还要了解轮子的内部构造,了解他是怎么咕噜起来的,终有一天,我们也要成为一个能够造轮子的人,因此,这篇文章被蛋生了。
这篇文章包括:
** 在开始之前,让我们先了解一些概念
我们先看一看官方API的解释:
注解是一种元数据形式,提供程序本身以外的数据,对代码没有直接的影响。
what?相信第一次听到注解的猴子肯定一脸懵逼,通俗的来讲,注解其实就是一个标签,就是给我们的程序打标签。
比如系统给我们提供的一些常见注解:
@Override :表示当前方法覆盖超类中的方法。如果你所写的方法和超类中的方法签名不同的话,编译器会报错。主要用于检查。
@Deprecated:表明当前的元素已经不适用。当使用了注解为@Deprecated的元素时,编译器会报出警告。
@SuppressWarnings : 关闭不当的编译器警告。
元注解:
在了解自定义注解之前,我们先来了解一下元注解,元注解是用在注解上的注解,主要分为以下几种:
1.@Target
表明当前注解可以用在什么目标之上。
ElementType主要包括一下几种:
2.@Retention
标识着什么时候处理这些注解,也标识着该注解的生命周期
@Documented 被修饰的注解会生成到javadoc中。
@Inherited 可以让注解类似被继承一样,但是这并不是真的继承。通过使用@Inherited,只可以让子类类对象使用getAnnotations()反射获取父类被@Inherited修饰的注解。
我们先来看一个简单的运行时注解案例,运用运行时注解实现view注入:
定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindV {
int value();
}
使用注解
public class MainActivity extends AppCompatActivity {
@BindV(R.id.button1)
Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterV.bind(this);
button1.setText("我也改名字了,好开心");
//此处省略1万行代码
}
因为我们用的是运行时注解,所以需要在运行时去解析注解,那肯定大家都想到了,对,就是运用反射解析注解(这里用到了反射的基础,如果有同学对反射不是很了解,可以先去了解下反射):
public class ButterV {
public static void bind(Activity activity) {
//获取类
Class extends Activity> clazz = activity.getClass();
//获取成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取被注解的元素
BindV bindV = field.getAnnotation(BindV.class);
if (bindV != null) {
//view id
int resId = bindV.value();
try {
//动态调用 activity的findViewById方法
Method findViewById = clazz.getMethod("findViewById", int.class);
Object view = findViewById.invoke(activity, resId);
//这里因为field.set()方法是私有的,不能直接赋值,需要先设置该属性的访问状态
field.setAccessible(true);
field.set(activity, view);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
到此为止一个完整的运行时注解小案例就完结了,大家是不是感觉很简单,大家会有所以问,难道辣么NB的注解框架就是这么实现的?我擦?恭喜你,你问到点子上了,当然没这么简单。
由于运行时注解的解析需要在运行时通过反射来解析,而在程序中运用大量的反射会影响程序运行效率,试想一下,我们的代码量很大的时候,在运行时还要运用反射去遍历所有view,显然,这不是我们想要的框架。
那么有没有在运行时之前就把这些view注入完成的方法呢,可能有的同学想到了,对就是编译时注解。
编译时注解实现ButterKnife注解注入
直接上代码(这里编译时创建类 xxxx_ViewBinding.java,的工具是 javapoet ,大家点击可以先了解下,有助于下面代码理解)
butterKnife库的依赖,见上图
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.8.0'
}
定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
//定义unbinder
public interface Unbinder {
@UiThread
void unbind();
}
//用于调用findviewbyid
public class Utils {
public static T findViewById(Activity activity, int resourceId) {
return (T) activity.findViewById(resourceId);
}
}
//编译时注解解析器
@AutoService(Processor.class)
public class ButterProcessor extends AbstractProcessor {
//获取所有被某个注解的元素
private Elements elementUtils;
//创建一个文件,并返回这个对象,以便用户写入数据
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}
/**
* 解析编译注解
*
* @param annotations 注解(TypeElement 标识一个类或者接口程序元素,枚举类型是一个类,注解类型是一个接口)
* @param roundEnv 注解处理工具框架将{@linkplain Processor#process提供带有对象的注解处理器实现此接口}以便处理器可以查询有关一轮注释处理的信息。
* @return boolean
*/
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取所有BindView注解修饰的Element集合
Set extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);
//将emements按照activity分类
Map> elementListMap = new LinkedHashMap<>();
for (Element element : elementsAnnotatedWith) {
//返回包裹此element的元素对象
Element enclosingElement = element.getEnclosingElement();
List bindViewElements = elementListMap.get(enclosingElement);
if (bindViewElements == null) {
bindViewElements = new ArrayList<>();
elementListMap.put(enclosingElement, bindViewElements);
}
bindViewElements.add(element);
}
//以activity为单位便利Map
for (Map.Entry> entry : elementListMap.entrySet()) {
Element enclosingElement = entry.getKey();
List bindViewElements = entry.getValue();
//TODO 编译时创建类 xxxx_ViewBinding类,在此类中。。。。、
//获取activity ClassName
String activityNameNameStr = enclosingElement.getSimpleName().toString();
ClassName activityName = ClassName.bestGuess(activityNameNameStr);
//获取接口Unbinder的ClassName,在编译时生成 xxxx_ViewBinding.java
ClassName unbinder = ClassName.get("com.c.butter", "Unbinder");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(unbinder)
.addField(activityName, "mActivity", Modifier.PRIVATE);
ClassName callSuper = ClassName.get("android.support.annotation", "CallSuper");
MethodSpec.Builder unbinderMethod = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addAnnotation(callSuper)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
MethodSpec.Builder constructorMethod = MethodSpec.constructorBuilder()
.addParameter(activityName, "mActivity")
.addModifiers(Modifier.PUBLIC)
.addStatement("this.mActivity = mActivity");
for (Element bindViewElement : bindViewElements) {
String fieldName = bindViewElement.getSimpleName().toString();
ClassName utils = ClassName.get("com.c.butter", "Utils");
int resourceId = bindViewElement.getAnnotation(BindView.class).value();
constructorMethod.addStatement("mActivity.$L = $T.findViewById(mActivity,$L)", fieldName, utils, resourceId);
unbinderMethod.addStatement("mActivity.$L = null", fieldName);
}
classBuilder.addMethod(constructorMethod.build())
.addMethod(unbinderMethod.build());
String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFile.builder(packageName, classBuilder.build())
.addFileComment("编译时生成的xxxx_ViewBinding文件")
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
@Override
public Set getSupportedAnnotationTypes() {
Set annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
运用编译时注解,在编译时动态生成了xxxx_ViewBinding.java类,然后再用户调用bind(activity)方法时,去创建这个对象,而在这个对象的构造器中我们就已经对activity的所有view进行了绑定,这样避免了在运行时用反射遍历activity的所有Fields,大大提高了效率。
最后,我们浏览一下自定生成的类是什么样子的:
public final class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
public MainActivity_ViewBinding(MainActivity target) {
this.target = target;
target.button = Utils.findViewById(target,2131165218);
}
@Override
@CallSuper
public final void unbind() {
target.button = null;
}
}
好了,到这里这次的分享就结束了,感谢大家的浏览,如果有什么不足,还望不吝批评。