注解(Annotation)自定义注解(四)--编译期注解解析讲解和手写ButterKnife
来源:互联网 发布:excel数据分类 编辑:程序博客网 时间:2024/05/15 10:38
注解(Annotation)自定义注解(四)–编译期注解解析讲解和手写ButterKnife
前言
前面两篇讲解了运行期注解的使用和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<Object> viewBinder = findViewBinderForClass(targetClass); if (viewBinder != null) { // 直接执行方法 viewBinder.bind(finder, target, source); } } catch (Exception e) { throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e); }}
如果从这里看我们好像看不到任何的东西,其实工作流程是怎样的呢?从上面介绍我们知道,编译器会先调用AbstractProcessor的子类的process函数,我们可以看一下Bind这个Annotation注解类和ButterKnifeProcessor这两个类:
ButterKnife 工作流程
@Retention(CLASS) @Target(FIELD)public @interface Bind { /** View ID to which the field will be bound. */ int[] value();}
当你编译你的Android工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作:
1.开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等。
2.当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口。
3.这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。
4.最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。
现在我们总该明白为什么我们的生成的属性和方法不能私有了吧?我们最后看一下编译时生成的class类吧
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> { @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类,有兴趣的同学可以自己看下。
手写BuffKnife
必备知识点:
- 基本的注解知识
- 反射
- 编译期注释处理API
- JavaPoet(用来生成java源文件,不是必须,可自己手动拼接)
- Auto (自动配置META-INF,不是必须,可以手动配置)
上面后面两个工具不是必须的,如果感兴趣,可以自己手动拼接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;
@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils = processingEnv.getElementUtils(); filer = processingEnv.getFiler(); typeUtils = processingEnv.getTypeUtils();}//返回支持的注解类型@Overridepublic Set<String> getSupportedAnnotationTypes() { return Collections.singleton(ViewInjector.class.getCanonicalName());}//类型与字段的关联表,用于在写入Java文件时按类型来写不同的文件和字段final Map<String, List<VariableElement>> map = new HashMap<String, List<VariableElement>>();//注解处理的核心方法@Overridepublic 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<VariableElement> cacheElements = map.get(className); if(cacheElements==null){ cacheElements = new LinkedList<VariableElement>(); } //将元素添加到该类型对应的字段列表中 cacheElements.add(varElement); //以类的路径为key,字段列表为value,存入map.这里是将所在字段按所属的类型进行分 map.put(className,cacheElements); } //生成java源文件 generate(); return false;} //生成java源文件private void generate() { Iterator<Map.Entry<String, List<VariableElement>>> iterator = map.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<String, List<VariableElement>> entry = iterator.next(); List<VariableElement> 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<MainActivity> { @Override public void inject(MainActivity target) { target.tv = (android.widget.TextView)(target).findViewById(2131492945); }}
我们再来看下是否真的可以使用:
看到这里我就放心了,哈哈。
下面很简单了,有兴趣,可以自己根据BuffKnife的bind来实现。
注解的知识已经讲完了,自己也学到很多东西,希望一直能够这样坚持下去。
下面给出下载地址:StoneViewInject
- 注解(Annotation)自定义注解(四)--编译期注解解析讲解和手写ButterKnife
- 注解(Annotation)自定义注解
- 自定义Annotation(注解)
- 自定义注解(Annotation)
- 通俗讲解Annotation注解(反射注解)
- Annotation(注解)_注解的自定义
- Java:注解(Annotation)自定义注解入门
- 注解(Annotation)自定义注解入门
- Java注解 (Annotation)自定义注解入门
- 注解(Annotation)自定义注解入门
- Java:注解(Annotation)自定义注解入门
- Annotation(自定义注解)反射获取注解
- 注解(Annotation)自定义注解入门
- 注解(Annotation)自定义注解入门
- 注解(Annotation)自定义注解入门
- 注解(Annotation)自定义注解入门
- 注解(Annotation)自定义注解入门
- JAVA 注解(Annotation)自定义注解入门
- c# 判断输入的号码是否是合法的手机号码
- android studio常见错误及异常处理记录
- java24
- 为工业机器人的学生分享一款ABB公司的机器人仿真软件
- 线程中CreateEvent和SetEvent及WaitForSingleObject的用法
- 注解(Annotation)自定义注解(四)--编译期注解解析讲解和手写ButterKnife
- 使用IntelliJ IDEA 配置Maven(入门)
- HTML基础---认识标签(body,p,hx,span,br, blockquote,q,address,code,pre)
- C++11线程安全队列
- libevent
- 微信小程序22
- poll&&epoll实现分析
- 二叉树的简单实现(递归算法)
- 微信小程序23