Android开发学习之路--APT技术
今年都快要过去了,也已经2个月没有写博客了,主要还是换了新工作,今年都好几家徘徊了,从最初的公司散伙,也快1年了,这么背的17年终于快要结束了。不过庆幸的是加入了目前的公司,一个暂时觉得可以锻炼自己的平台。从嵌入式到app到嵌入式android系统,这次又回到了app,希望这次可以深耕3-5年,能在移动互联网站稳脚跟。两个月的时间忙于熟悉了解公司业务,也少了自己学习的时间,机器学习还没继续,android也没有深入了解,是时候补一把了。
以前遇到Dagger2, ButterKnife, EventBus3等的都是直接用,也没有太关心内部的源码实现。当想看的时候,发现一堆的注解不是非常好理解,所以还是先打打基础学习下apt技术吧,虽然不是那么新鲜了。
1.前言
首先看下butterknife生成的代码,要是都自己来敲,那就没法去和女神约会潇洒了。作为有家室的,也得多留点时间陪媳妇。
@BindView(R.id.iv_left_menu)
ImageView ivLeftMenu;
@BindView(R.id.iv_add)
ImageView ivAdd;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.rv_content)
RecyclerView rvRobots;
@BindView(R.id.coordinator_layout)
LinearLayout coordinatorLayout;
然后实际的生成的代码如下:
public class Main2Fragment_ViewBinding implements Unbinder {
private Main2Fragment target;
@UiThread
public Main2Fragment_ViewBinding(Main2Fragment target, View source) {
this.target = target;
target.ivLeftMenu = Utils.findRequiredViewAsType(source, R.id.iv_left_menu, "field 'ivLeftMenu'", ImageView.class);
target.ivAdd = Utils.findRequiredViewAsType(source, R.id.iv_add, "field 'ivAdd'", ImageView.class);
target.toolbar = Utils.findRequiredViewAsType(source, R.id.toolbar, "field 'toolbar'", Toolbar.class);
target.rvRobots = Utils.findRequiredViewAsType(source, R.id.rv_content, "field 'rvRobots'", RecyclerView.class);
target.coordinatorLayout = Utils.findRequiredViewAsType(source, R.id.coordinator_layout, "field 'coordinatorLayout'", LinearLayout.class);
}
@Override
@CallSuper
public void unbind() {
Main2Fragment target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.ivLeftMenu = null;
target.ivAdd = null;
target.toolbar = null;
target.rvRobots = null;
target.coordinatorLayout = null;
}
}
2.注解
那么他是怎么实现的?在此之前需要了解**解,可以参考下之前的一篇博客:
Android开发学习之路–Annotation注解简化view控件之初体验
一年前刚接触的时候写的,勉强还能理解哈,这里主要还是实现了运行时的注解,用了反射肯定是需要消耗一定的性能。上述的butterknife生成的代码可是编译时生成的代码,基本不消耗性能的。
3.什么是APT
理解了注解后,我们开始学习apt吧。
- APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会将编译生成的源文件和原来的源文件一起生成class文件。
- annotationProcessor:APT工具中的一种,他是google开发的内置框架,不需要引入,具体可以直接在build.gradle中使用,比如butterknife和dagger2引入如下:
dependencies {
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
compile 'com.google.dagger:dagger:2.12'
annotationProcessor 'com.google.dagger:dagger-compiler:2.12'
}
4.APT注解的流程
- 1.定义注解(如@automain)
- 2.定义注解处理器
- 3.在处理器里面完成处理方式,通常是生成java代码。
- 4.注册处理器
- 5.利用APT完成如下图的工作内容。
5 APT的简单实现
既然了解了apt的过程,那么就来实现下了。
5.1 定义注解
新建一个java库,命名为annotation。
BindView注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
Onclick注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface OnClick {
int[] value();
}
5.2 定义注解处理器
新建一个java库,命名为processor。
主要继承AbstractProcessor实现process方法生成对应的代码,至于AbstractProcessor何时被调用,怎么执行的,还没有做过多深入理解。利用了AutoService:
https://github.com/google/auto/tree/master/service。看一段官方的话:
AutoService will generate the file
META-INF/services/javax.annotation.processing.Processor in the output
classes folder.
在process方法里扫描所有的BindView和OnClick注解,然后javapoet来生成代码,这里生成代码的class为MAinActivity$$Finder.class,具体的实现可以参考github的例子,已经注释得很清楚了(例子源码在文末的链接中)。
若想要更深入理解javapoet的使用,可以参考这里:javapoet
5.3 编译生成代码
编译后在app/build/generated/source/apt/debug/com/jared/helloapt目录下会生成对应的MainActivity$$Finder.class文件
public class MainActivity$$Finder implements Finder<MainActivity> {
@Override
public void inject(final MainActivity host, Object source, Provider provider) {
host.tvInfo = (TextView)(provider.findView(source, 2131165309));
View.OnClickListener listener;
listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
host.onClick(view);
}
};
provider.findView(source, 2131165219).setOnClickListener(listener);
provider.findView(source, 2131165220).setOnClickListener(listener);
}
}
很明显这里就是我们平时写的findview啊,setOnclickLIstener等方法的具体实现。通过注解,然后自动生成代码,注入到我们需要的类中。就免去了很多的重复劳动力,而且不会影响代码的运行效率。
5.4 注册处理器
新建一个android的library,命名为viewfinder。
实现ViewFinder的static的inject方法,用于注入代码。最后调用到注解处理器中生成的xxx$$Finder类的inject方法。其中这里xxx既是MainActivity。
6.总结下
这里我们再把整个过程理一遍。
编译期间
processor模块里的自定义的MyProcessor类的process会扫描所有的注解,然后生成自定义的XXX$$Finder.class代码。
使用期间
首先使用注解@BindView(R.id.tv_info)以及@OnClick({R.id.bt_change, R.id.bt_reset}),
接着MainActivity的onCreate方法中调用
ViewFinder.inject(this);方法来注入编译期间生成的代码。
关于apt技术基本上也了解了,说了那么多其实把例子敲一遍就都明白了。
github源码例子点击这里
参考:
http://blog.chengyunfeng.com/?p=1021
http://blog.csdn.net/u011315960/article/details/64441120
http://blog.csdn.net/hj7jay/article/details/52180023
https://github.com/sockeqwe/annotationprocessing101
上一篇: Android技术周报_W8