前面两篇讲解了运行期注解的使用和xutils源码的解析,以及手动打造自己的IOC框架。但是运行期注解由于性能问题被一些人所诟病,所以这里我们讲下编译器注解的使用和实战。
编译器注解的核心原理依赖APT(Annotation Processing Tolls)实现,例如,我们常用的ButterKnife,Dagger,Retrofit等开源库都是基于APT的。那么编译器注解是如何工作的呢?这就是我们要讨论的内容。
编译时Annotation注解的基本原理是,在某些代码元素上(比如类型,函数,字段)添加注解,然后在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的元素传递给process中,使得开发者可以在编译期可以进行处理,比如,根据注解生成新的ava类,这也是BufferKnife等库的基本原理。
编译处理时,是分开进行的。如果在某个处理中产生的新的JAVA文件,那么就需要另外一个处理来处理新生成的源文件,反复进行,知道没有新的文件产生。在处理完文件后,再进行编译。JDK5提供了APT工具来对注解进行处理。编写注解处理器和核心是:AnnotationProcessorFactory和AnnotationProcessor两个接口,前者是为某些注解类型创建注解处理器工厂,后者是表示注解处理器。
对于编译器,代码中元素的结构是不会变的。JDK中为这些元素定义了一个基本类型Element类,它有几个子类:
PackageElement--包元素,包含包的信息
TypeElement-------类型元素,描述字段信息
ExecutableElement--可执行元素,代表函数类型的元素
VariableElement------变量元素
TypeParameterElement--类型参数元素
使用上面的抽象来对于代码中的基本元素。
我们先来分析BuffKnife使用以及源码,然后在手动打造我们自己的框架。
BuffKnife使用
BuffKnife项目
public class MainActivity extends AppCompatActivity {
@Bind(R.id.icon)
ImageView icon;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.icon)
public void onClick() {
Toast.makeText(this, "图片点击了", Toast.LENGTH_LONG).show();
}
}
使用非常简单,主要注意:属性是不能private就行。
butterknife的源码解析
按照逻辑,我们应该先看ButterKnife.bind(this),下面是源码:
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
ViewBinder
如果从这里看我们好像看不到任何的东西,其实工作流程是怎样的呢?从上面介绍我们知道,编译器会先调用AbstractProcessor的子类的process函数,我们可以看一下Bind这个Annotation注解类和ButterKnifeProcessor这两个类:
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
int[] value();
}
现在我们总该明白为什么我们的生成的属性和方法不能私有了吧?我们最后看一下编译时生成的class类吧
public class MainActivity$$ViewBinder implements ViewBinder {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131427372, "field 'icon' and method 'onClick'");
target.icon = finder.castView(view, 2131427372, "field 'icon'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(View p0) {
target.onClick();
}
});
}
@Override public void unbind(T target) {
target.icon = null;
}
}
这里没有详细分析ButterKnifeProcessor类,有兴趣的同学可以自己看下。
上面后面两个工具不是必须的,如果感兴趣,可以自己手动拼接java源文件和手动配置Auto。
基本的配置:
这边要注意几点:
aptlib和annotationlib是java库,不用android库是因为androidSDK中没有javax.annotation.*
android程序调用java库时,需要在java库中引入com.google.android:android:XXXX和com.android.support:support-annotations:XXXXjar包
想使用APT,项目要应用APT插件:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
然后引入javajar包:
apt project(':aptlib')
compile project(':annotationlib')
知道上面的知识,就可以开始写我们的代码了。
让我们先来看看注解类吧
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewInjector {
int value();
}
//自动配置META-INF
@AutoService(Processor.class)
public class ViewInjectProcessor extends AbstractProcessor {
//在元素上进行操作的某些实用工具方法
private Elements elementUtils;
//用来创建新源、类或辅助文件的 Filer
private Filer filer;
//用来在类型上进行操作的某些实用工具
private Types typeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
typeUtils = processingEnv.getTypeUtils();
}
//返回支持的注解类型
@Override
public Set getSupportedAnnotationTypes() {
return Collections.singleton(ViewInjector.class.getCanonicalName());
}
//类型与字段的关联表,用于在写入Java文件时按类型来写不同的文件和字段
final Map> map = new HashMap>();
//注解处理的核心方法
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取使用ViewInjector注解的所有元素
Set extends Element> elementSet = roundEnv.getElementsAnnotatedWith(ViewInjector.class);
for (Element element:elementSet) {
checkAnnotationValid(element,ViewInjector.class);
//注解的字段
VariableElement varElement= (VariableElement)element;
//类型的完整路径名,比如某个Activity的完整路径
String className = getParentClassName(varElement);
//获取这个类型的所有注解,例如某个Activity中的所有View的注解对象
List cacheElements = map.get(className);
if(cacheElements==null){
cacheElements = new LinkedList();
}
//将元素添加到该类型对应的字段列表中
cacheElements.add(varElement);
//以类的路径为key,字段列表为value,存入map.这里是将所在字段按所属的类型进行分
map.put(className,cacheElements);
}
//生成java源文件
generate();
return false;
}
//生成java源文件
private void generate() {
Iterator>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry> entry = iterator.next();
List cacheElements = entry.getValue();
if (cacheElements == null || cacheElements.size() == 0) {
continue;
}
InjectorInfo info = InjectorInfoUtil.createInjectorInfo(processingEnv,cacheElements.get(0));
//下面全是JavaPoet的基本使用,不明白的可以点击上面的链接去看
final ClassName className = ClassName.get(info.packageName,info.classlName);
final ClassName InterfaceName=ClassName.get(InjectAdapter.class);
MethodSpec.Builder injectsBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(void.class)
.addParameter(className, "target");
for (VariableElement element:cacheElements) {
InjectorInfo temInfo = InjectorInfoUtil.createInjectorInfo(processingEnv,element);
ViewInjector annotation = element.getAnnotation(ViewInjector.class);
int value = annotation.value();
String fieldName = element.getSimpleName().toString();
String type = element.asType().toString();
injectsBuilder.addStatement("target." + fieldName + " = ("+type+")(target).findViewById(" + value + ")");
}
MethodSpec injects = injectsBuilder.build();
TypeSpec typeSpec = TypeSpec.classBuilder(info.newClassName)
.addSuperinterface(ParameterizedTypeName.get(InterfaceName, className))
.addModifiers(Modifier.PUBLIC)
.addMethod(injects)
.build();
JavaFile javaFile = JavaFile.builder(info.packageName, typeSpec).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace(); }
}
}
/*打印错误的方法 */
protected void error(Element element, String message, Object... args) {
if (args.length > 0) { message = String.format(message, args);
}
processingEnv.getMessager().printMessage(ERROR, message, element);
}
//类型的完整路径名,比如某个Activity的完整路径
private String getParentClassName(VariableElement varElement) {
TypeElement typeElement = (TypeElement) varElement.getEnclosingElement();
String packageName = AnnotationUtil.getPackageName(processingEnv,typeElement);
return packageName + "." + typeElement.getSimpleName().toString();
}
}
测试:
public class MainActivity extends AppCompatActivity {
@ViewInjector(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewInjectUtil.injectView(this);
tv.setText("fddfdfdfdfdfdfdfdfdfdf");
}
}
重新编译一下,到当前目录app\build\generated\source\apt\debug\com\github\essayjoke\可以看到MainActivity$InjectAdapter.java文件,我们打开看一下:
package github.com.stoneviewinject;
import com.example.InjectAdapter;
import java.lang.Override;
public class MainActivity$$InjectAdapter implements InjectAdapter {
@Override
public void inject(MainActivity target) {
target.tv = (android.widget.TextView)(target).findViewById(2131492945);
}
}
我们再来看下是否真的可以使用:
看到这里我就放心了,哈哈。
下面很简单了,有兴趣,可以自己根据BuffKnife的bind来实现。
注解的知识已经讲完了,自己也学到很多东西,希望一直能够这样坚持下去。
下面给出下载地址:StoneViewInject