不同于运行时注入的思想,编译时注入主要是借助APT工具,在编译时生成辅助类,运行时通过反射加载辅助类,执行其构造方法,来达到解耦,方便编程的目的.
编译时注入比较流行的框架有Butterknife,Dagger2,针对android平台有daggerAndroid,实际daggerAndroid是对dagger2的优化,因为dagger2使用过程中有点违反了注入的核心思想(在注入的类里面,能够看到提供注入的类的对象),而daggerAndroid解决了这个问题.
Butterknife,dagger2通常会搭配MVC,MVP去使用,如果你使用了MVVM,那就不需要使用了,原因是MVVM中的对象,在后台使用DataBinding去生成了,这个原理跟Butterknife,dagger2实现是类似的.
至于DataBinding,Butterknife,dagger2的具体使用,我们这里不讨论,网上有很多的介绍文档,这里主要是看它的实现原理,就是你按照框架的用法加了那几行代码,怎么实现的相应的功能.
其实,还是要先沾几行代码,不然上面说"那几行代码",你一定会迷糊,到底是那几行呢?
下面的代码Butterknife使用的demo.
添加依赖库,module的build.gradle
dependencies {
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
}
public class MainActivity extends AppCompatActivity {
@BindView(R.id.button)
public Button mBtn;
@BindView(R.id.imageView)
protected ImageView mIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.button)
public void onClick(View view) {
}
}
上面就是Butterknife的简单实用,除了控件对象上的@BindView的注解,事件监听上的@OnClickd的注解,还有OnCreate中的BuffterKnife.bind(this)的调用,
这里要讨论的是,这几行代码是怎么实现的控件对象的赋值,及监听事件的调用的.
在使用了Butterknife的项目编译后,在app/build/intermediates目录会多一个MainActivity_ViewBinding.class文件.
这个文件时借助APT工具生成的,APT(Annotation Processing Tools)是一个处理注解的工具,在编译使用这个工具扫描和处理注解,来生成.class文件,
到这里,应该就明白了,这些框架的原理就是通过注解,借助APT工具,生成了一些辅助类.
然后在运行时,把生成辅助类加载到内存,利用反射调用辅助类的构造函数等方法.
APT工具,更确切的说时一个javac的工具,只是去处理注解,不会去影响源文件.它作用的过程,在如下图的位置:
Javac对Java文件的编译过程:,
- 解析和填充符号表过程。
- 插入式注解处理器的注解处理过程。
- 分析和字节码生成过程。
图中的Round意思:如果注解处理器在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每次循环称为一个Round
看完了辅助文件的生成过程,再来看Butterknife是怎么实现绑定功能的,可以想到,绑定的实现就是辅助文件的被调用执行的过程。
启动是从MainActivity的onCreate方法中的
ButterKnife.bind(this); 这句调用开始的。
Butterknife.java,这里的DecorView是View树的根view,包括了应用自定义的contentView及装饰条。
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
下面是加载MainActivity_ViewBinding.class类,并调用其构造方法的过程:
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
String clsName = cls.getName();
//如果是android framework中的类,或者java公共的api,就不做处理
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//代码执行到这里,apt生成的那个辅助类,就被加载进内存了。然后调用构造方法,就把类实例构建出来了。
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
接着看辅助类的构造函数,
MainActivity_ViewBinding.class
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view = Utils.findRequiredView(source, 2131165251, "field 'mBtn' and method 'onClick'");
target.mBtn = (Button)Utils.castView(view, 2131165251, "field 'mBtn'", Button.class);
this.view7f070043 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View p0) {
target.onClick(p0);
}
});
target.mIv = (ImageView)Utils.findRequiredViewAsType(source, 2131165290, "field 'mIv'", ImageView.class);
}
跟进去看下构造函数中调用的Utils中两个方法的实现:
public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
Class<T> cls) {
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
try {
return cls.cast(view);
} catch (ClassCastException e) {
String name = getResourceEntryName(view, id);
}
}
一个是通过findViewById来获取到View,另一个把view转成具体类型,赋值给MainActivity中的控件对象,然后如果有事件绑定,在生成事件绑定的代码.这样这个框架就完成了对象绑定,和事件绑定.
前面这么多内容都是铺垫,接下来讨论的才是这篇文章的重点, 就是这个辅助文件是怎么生成的,当然下面的实现不会写的像Buterknife那么复杂,重点是弄明白这个框架的实现原理.
下面就是新建一个app,然后,自定义注解,自定义注解处理器,实现对象,事件的绑定.这里分三个模块:
1),app本身,
2),annotations,提供注解的定义,供整个框架使用,
3),annotation_compiler,注解处理模块,来处理注解.
这三个模块间的关系,
app依赖annotations, annotation_compiler,
Annotation_compiler依赖annotations, 这两个模块都是运行在电脑端的,是在编译期起作用,所以选择的module类型是java library.
在app的build.gradle中修改下annotation_compiler的依赖配置,因为他是处理注解的,所以改为annotationProcessor,以方便使用annotation相关的语法.
在annotations模块中,定义一个用在控件Button上的注解:
//这个注解应用在按钮,
//保留到源码阶段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
在App模块定义一个接口类,ButterKnifeIBinder.java,提供一个bind方法,功能跟ButterKnife的bind方法类似,用于绑定Activity使用,这里定义成一个泛型类,可以绑定多种Activity.
public interface ButterKnifeIBinder<T> {
void bind(T activity);
}
在annotations_compiler模块中的build.gradle中,注册下使用APT的功能,
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(path: ':annotations')
//注册APT的功能
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
}
到这里,准备工作已就绪,下面就开核心代码的编写,我把注释直接写在代码中,
//注解处理器的功能类,扫描注解,生成辅助类
//使用前,要用注解@AutoService(Process.class)注册下,这个注册就类似与在manifest里面注册四大组件,
说明下代码处理的逻辑:
1,拿到项目中所有应用了属性(@BindView)注解的VariableElements,这是个map集合,键值是Activity的全类名,
2,拿到项目中所有应用了事件(@OnClick)注解的ExecutableElements.这是个map集合,键值是Activity的全类名
对于上面这两类elements集合,是包含了项目中所有Activity中的应用了这两个注解的属性,事件,但是我们生成辅助类时,是要每个Activity生成单独的文件,所以会对这两个集合重新分类,把属于同一个Activity的放入同一个集合,
3,拿到所有需要处理的activity的集合,在每个需要处理的activity元素中,分别拼接属性代码,事件代码.
4,属性赋值,方法代码的生成,就是字符串的拼接.
@AutoService(Processor.class)
public class AnnotationnCompiler extends AbstractProcessor {
Logger logger = Logger.getLogger(AnnotationnCompiler.class.toString());
Filer mFiler;
final String mIndentSpace1 = " ";
final String mIndentSpace2 = " ";
final String mIndentSpace3 = " ";
//使用APT处理那些注解,
// 注意这里导入BindView是注解模块中我们自己定义的注解类:
//import com.test.annotations.BindView;
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
//支持的java的版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//获取一个用来生成辅助类文件的对象,
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
}
//在这个方法中,根据需要,完成APT的功能,
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<String> activitySets = new HashSet<>();
//参数annotations,拿到我们关注的那个文件中的所有注解,这里指的是MainActivity中的所有注解,
//参数roundEnv,上下文的环境变量,
//获取使用BindView注解的控件对象,这里获取的是一个集合,就是在MainActivity中,
// 所有用BindView注解的控件对象都会返回过来,Element是对控件(如Button,ImageView)的一个封装。
// Element有几个具体的实现类,TypeElement,MethodElement,VarialbeElement.
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);
//需要注意的一点,elementsAnnotatedWith这集合获取到的是整个项目中,所有类中,使用了BindView注解的属性,
// 比如项目中有3个Activity,每个Activity都有被BindView注解的属性Button,
// 那么这个elementsAnnotatedWith集合拿到的是3个activity中的所有被BindView注解的属性Button,
// 但是我们在生成辅助类时,每个activity是要单独生成一个辅助类的,
// 所以这里要分离出每个activity中的属性,然后用map分别保存下来,
// 其中map 的key就是activity对象,value是属性集合。
Map<String, List<VariableElement>> mapVariable = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
VariableElement variableElement = (VariableElement)element;
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
activitySets.add(activityName);
List<VariableElement> variableElements = mapVariable.get(activityName);
if (null == variableElements) {
variableElements = new ArrayList<>();
mapVariable.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
//事件集合的处理
Set<? extends Element> elementsAnnotatedMethods = roundEnv.getElementsAnnotatedWith(OnClick.class);
Map<String, List<ExecutableElement>> mapExecutable = new HashMap<>();
for (Element elementsAnnotatedMethod : elementsAnnotatedMethods) {
ExecutableElement executableElement = (ExecutableElement) elementsAnnotatedMethod;
String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
activitySets.add(activityName);
List<ExecutableElement> executableElements = mapExecutable.get(activityName);
if (null == executableElements) {
executableElements = new ArrayList<>();
mapExecutable.put(activityName, executableElements);
}
executableElements.add(executableElement);
}
//开始生成文件
if (activitySets.size() > 0) {
for (String activityName : activitySets) {
String packageName = null;
Writer writer = null;
try {
if (mapVariable.size() > 0) {
List<VariableElement> variableElementList = mapVariable.get(activityName);
//packageName
TypeElement enclosingElement = (TypeElement) variableElementList.get(0).getEnclosingElement();
packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
//生成文件,就是字符串拼接
JavaFileObject sourceFile = mFiler.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
fileBegin(writer, packageName, activityName);
variableBuild(writer, variableElementList);
}
if (mapExecutable.size() >0) {
List<ExecutableElement> executableElementList = mapExecutable.get(activityName);
if (null == packageName || null == writer) {
//packageName
TypeElement enclosingElement = (TypeElement)executableElementList.get(0).getEnclosingElement();
packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
JavaFileObject sourceFile = mFiler.createSourceFile(packageName+"."+activityName+"_ViewBinding");
writer = sourceFile.openWriter();
fileBegin(writer, packageName, activityName);
}
executableBuild(writer, executableElementList);
}
fileEnd(writer);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != writer) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
//生成类文件中,公共的部分
private void fileBegin(Writer writer, String packageName, String activityName) throws IOException {
writer.write("package " + packageName + ";\n\n");
writer.write("import " + "android.view.View;\n");
writer.write("import " + packageName + ".ButterKnifeIBinder;\n\n");
writer.write("public class "+activityName+"_ViewBinding implements ButterKnifeIBinder<"+packageName+"."+activityName+"> {\n");
writer.write(mIndentSpace1+"private "+activityName +" mTarget;\n");
writer.write(mIndentSpace1+"@Override\n");
writer.write(mIndentSpace1+"public void bind("+packageName+"."+activityName+" target) {\n");
writer.write(mIndentSpace2+"mTarget = "+ "target;\n");
}
private void fileEnd(Writer writer) throws IOException{
writer.write("\n"+mIndentSpace1+"}");
writer.write("\n}");
}
//生成属性赋值的代码
private void variableBuild(Writer writer, List<VariableElement> variableElementList) throws IOException{
for (VariableElement variableElement : variableElementList) {
String variableName = variableElement.getSimpleName().toString();
int id = variableElement.getAnnotation(BindView.class).value();
TypeMirror typeMirror = variableElement.asType();
writer.write(mIndentSpace2+"mTarget."+variableName+"=("+typeMirror+")mTarget.findViewById("+id+");\n");
}
}
//生成方法代码
private void executableBuild(Writer writer, List<ExecutableElement> methodsElementList) throws IOException{
writer.write("\n");
for (ExecutableElement executableElement : methodsElementList) {
String methodName = executableElement.getSimpleName().toString();
List<? extends VariableElement> parameters = executableElement.getParameters();
writer.write("//"+parameters.get(0)+"\n");
OnClick annotationOnClick = executableElement.getAnnotation(OnClick.class);
Class<? extends Annotation> annotationClass = annotationOnClick.annotationType();
ListenerClass annotationListenerClass = annotationClass.getAnnotation(ListenerClass.class);
String listenerSetter = annotationListenerClass.listenerSetter();
String listenerType = annotationListenerClass.listenerType();
String callbackMethod = annotationListenerClass.callbackMethod();
String parameters1 = annotationListenerClass.parameters();
int fieldId = annotationOnClick.value();
writer.write(mIndentSpace2+"View view"+fieldId +"=(View)mTarget.findViewById("+fieldId+");\n");
writer.write(mIndentSpace2+"view"+fieldId+"."+listenerSetter+"(new "+listenerType+"() {\n"+
mIndentSpace3+"@Override\n"+
mIndentSpace3+"public void "+callbackMethod+"("+parameters1+" parameter) {\n"+
mIndentSpace3+mIndentSpace1+"mTarget."+methodName+"(parameter);\n"+
mIndentSpace3+"}\n"+
mIndentSpace2+"});\n");
}
}
}
事件注解类,因为要获取事件的处理要素,用到了注解的多态。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@ListenerClass(
listenerSetter = "setOnClickListener",
listenerType = "android.view.View.OnClickListener",
callbackMethod = "onClick",
parameters = "android.view.View")
public @interface OnClick {
int value();
}
@Target(ElementType.ANNOTATION_TYPE)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface ListenerClass {
String listenerSetter();//表示订阅关系
String listenerType();//表示事件本身
String callbackMethod();//表示事件处理的方法名
String parameters();//方法参数,
}
属性上的注解类:
package com.test.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
辅助类生成出来后,怎么使用,类似ButterKnife的用法,利用反射去生成辅助类的对象,然后调用其bind方法,把Activity传进去。
package com.test.butterknifeframe;
import android.app.Activity;
public class InjectButterKnife {
public static void bind(Activity activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
Class<?> aClass = Class.forName(name);
ButterKnifeIBinder binder = (ButterKnifeIBinder)aClass.newInstance();
binder.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
如下是MainActivity的完整代码,应用了两个事件注入,一个属性注入。
import com.test.annotations.BindView;
import com.test.annotations.OnClick;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectButterKnife.bind(this);
btn.setText("IOCButton");
}
@OnClick(R.id.btn2)
public void btn2Click(View view) {
Log.d("MainActivity","btn2,IOC button click.");
}
@OnClick(R.id.btn3)
public void btn3Click(View view) {
Log.d("MainActivity","btn3,IOC button click.");
}
}
最后看下,生成的辅助类的代码,存放路径:D:\ButterKnifeFrame\app\build\generated\source\apt\debug\com\test\butterknifeframe
package com.test.butterknifeframe;
import android.view.View;
import com.test.butterknifeframe.ButterKnifeIBinder;
public class MainActivity_ViewBinding implements ButterKnifeIBinder<com.test.butterknifeframe.MainActivity> {
private MainActivity mTarget;
@Override
public void bind(com.test.butterknifeframe.MainActivity target) {
mTarget = target;
mTarget.btn=(android.widget.Button)mTarget.findViewById(2131165218);
//view
View view2131165219=(View)mTarget.findViewById(2131165219);
view2131165219.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View parameter) {
mTarget.btn2Click(parameter);
}
});
//view
View view2131165220=(View)mTarget.findViewById(2131165220);
view2131165220.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View parameter) {
mTarget.btn3Click(parameter);
}
});
}
}
这里是以Button控件赋值,OnClick事件绑定为例,其他属性,事件的处理都是一个套路.