ButterKnife 的实现原理
来源:互联网 发布:linux shell脚本攻略 编辑:程序博客网 时间:2024/05/22 08:12
下面我们来看看 ButterKnife 的简单使用。
首先我们看在没有使用ButterKnife时,我们初始化一个Activity中的各个控件的代码:
[code]public class ExampleActivity extends Activity {
TextView title;
ImageView icon;
TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 通过findViewById进行视图查找,然后进行类型转换
title = (TextView) findViewById(R.id.title);
icon = (ImageView) findViewById(R.id.icon);
footer = (TextView) findViewById(R.id.footer);
}
}[/code] 在ExampleActivity函数的onCreate函数中,我们通常会对各个子视图进行初始化,这些代码看起来重复性很高,而且丑陋不堪,几乎都要对View进行强转,当一个布局中含有十个以上的View时,再加上为某些View添加上事件处理等,这部分的代码将占用很大的篇幅。ButterKnife就是为了简化这些工作而出现的,让开发人员专注在真正有用的代码上。使用ButterKnife之后我们的代码变成了这样:
[code]public class ExampleActivity extends Activity {
@InjectView(R.id.title) TextView title;
@InjectView(R.id.icon) ImageView icon;
@InjectView(R.id.footer) TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 将Activity注入ButterKnife
ButterKnife.inject(this);
}
}[/code] 当运行完onCreate函数之后Activity中的几个View就已经被初始化了。findViewById、强制转换等样板代码被去除了,代码变得更加简单,使得我们可以更专注在代码逻辑的编写上,整个类型也更易于维护。
那么ButterKnife的原理是什么呢?@InjectView又是什么?ButterKnife的inject函数又有什么作用?
这是因为ButterKnife使用了一种叫做编译时注解的技术(即APT), 代码在编译时会扫描AbstractProcessor的所有子类,并且调用这些子类的process函数,在这个函数就会将所有的代码元素传递进来。此时我们只需要在这个process函数中获取所有添加了某个注解的元素,然后对这些元素进行操作,使之能够满足我们的需求,这样我们就可以在编译期对源代码进行处理,例如生成新的类等。在运行时,我们通过一些接口对这些新生成的类进行调用以此完成我们的功能 。
说了这么多还是太抽象了,还是以小民的例子来为大家一一解除疑问吧。
小民自从知道ButterKnife之后也被它的魅力所吸引了,于是决定研究个究竟,经过一番搜索得知ButterKnife是基于编译时注解,然后通过APT生成辅助类,然后在运行时通过inject函数调用那些生成的辅助类来完成功能。小民决定自己写一个只支持View 的id注入的简版ButterKnife来深入学习,这个库被命名为SimpleDagger。
[code]首先小民建了一个注解类,代码如下 :[/code] [code]@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewInjector {
int value();
}[/code] 因为我们的这个注解只支持View的id注入,因此它的目标元素是字段,它只存在在class文件中,因为一旦过了编译期我们就不再需要它了。关于注解方面的基础知识我们不做过多讲解,对这方面不了解的同学可以先阅读相关书籍,例如《Java编程思想》、《Java核心技术》。
在添加AbstractProcessor 之前,为了使Eclipse支持 APT 需要一些配置,可以参考 injectdagger 。Android Studio要支持 APT则需要添加APT插件,有兴趣的同学可以自行搜索相关解决方案。
通过 APT 来生成辅助类型
添加这个注解之后,我们还需要在编译期对这个注解进行处理。上文说到,编译器会在编译时检测所有的AbstractProcessor并且调用它的process函数来让开发人员对代码元素进行处理。因此我们新建一个AbstractProcessor的子类,代码如下 :
[code]@SupportedAnnotationTypes("org.simple.injector.anno.*")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectorProcessor extends AbstractProcessor {
//所有注解处理器的列表
List mHandlers = new LinkedList();
//类型与字段的关联表,用于在写入Java文件时按类型来写不同的文件和字段
final Map<string, list> map = new HashMap<string, list>();
// 生成辅助累的Writer类
AdapterWriter mWriter;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 注册注解处理器
registerHandlers();
// 初始化代码生成器
mWriter = new DefaultJavaFileWriter(processingEnv);
}
// 注册处理器
private void registerHandlers() {
mHandlers.add(new ViewInjectHandler());
}
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
// 迭代所有的注解处理器,使得每个注解都有一个处理器,
for (AnnotationHandler handler : mHandlers) {
// 关联ProcessingEnvironment
handler.attachProcessingEnv(processingEnv);
// 解析注解相关的信息
map.putAll(handler.handleAnnotation(roundEnv));
}
// 将解析到的数据写入到具体的类型中
mWriter.generate(map);
return true;
}
// 代码省略
}[/code] 在ViewInjectorProcessor类的上面我们看到如下注解@SupportedAnnotationTypes(“org.simple.injector.anno.*”), 这个注解表明这个类只支持org.simple.injector.anno路径下的注解,我们的ViewInjector注解就是在这个包下。在该类的init函数中我们注册了一个注解处理器,也就是ViewInjectHandler类,该类实现了AnnotationHandler接口,该接口的声明如下 :
[code]// 注解处理接口
public interface AnnotationHandler {
// 关联ProcessingEnvironment
void attachProcessingEnv(ProcessingEnvironment processingEnv);
// 处理注解,将结果存储到Map中
Map<string, list> handleAnnotation(RoundEnvironment env);
}[/code] 该接口声明了两个函数,一个是关联ProcessingEnvironment,另一个是handleAnnotation函数,负责处理标识了ViewInjector注解的元素。小民的设计思路是定义一个AnnotationHandler接口,每个实现类处理一种类型的注解,例如ViewInjectHandler只处理ViewInject注解。下面我们看看ViewInjectHandler的核心代码 :
[code]public class ViewInjectHandler implements AnnotationHandler {
ProcessingEnvironment mProcessingEnv;
@Override
public void attachProcessingEnv(ProcessingEnvironment processingEnv) {
mProcessingEnv = processingEnv;
}
@Override
public Map<string, list> handleAnnotation(RoundEnvironment roundEnv) {
Map<string, list> annotationMap = new HashMap<string, list>();
// 1、获取使用ViewInjector注解的所有元素
Set elementSet = roundEnv.getElementsAnnotatedWith(ViewInjector.class);
for (Element element : elementSet) {
// 2、获取被注解的字段
VariableElement varElement = (VariableElement) element;
// 3、获取字段所在类型的完整路径名,比如一个TextView所在的Activity的完整路径,也就是变量的宿主类
String className = getParentClassName(varElement);
// 4、获取这个宿主类型的所有元素,例如某个Activity中的所有注解对象
List cacheElements = annotationMap.get(className);
if (cacheElements == null) {
cacheElements = new LinkedList();
}
// 将元素添加到该类型对应的字段列表中
cacheElements.add(varElement);
// 以宿主类的路径为key,所有字段列表为value,存入map.
// 这里是将所在字段按所属的类型进行分类
annotationMap.put(className, cacheElements);
}
return annotationMap;
}
// 代码省略
}[/code] 在handleAnnotation函数中小民获取了所有被ViewInject注解标识了的VariableElement元素,然后将这些元素按照宿主类进行分类存到一个map中,key就是宿主类的完整类路径,value就是这个宿主类中的所有被标识了ViewInject的VariableElement元素列表。例如将上述ExampleActivity的示例替换成小民的SimpleDagger,使用ViewInject注解标识中三个View,代码如下 :
[code]package com.simple.apt;
public class ExampleActivity extends Activity {
@ViewInject (R.id.title) TextView title;
@ViewInject (R.id.icon) ImageView icon;
@ViewInject (R.id.footer) TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 其他代码暂时省略
SimpleDagger.inject(this);
}
}[/code] 那么此时ExampleActivity的完整路径为com.simple.apt.ExampleActivity,这个完整路径我们可以通过VariableElement元素获取到,这些VariableElement就是代表了ExampleActiivty中的title、icon、footer三个对象。因此通过ViewInjectHandler的handleAnnotation处理之后我们的map中就含有了以com.simple.apt.ExampleActivity为key,以title、icon、footer三个成员变量对应的VariableElement列表为value的数据。
首先我们看在没有使用ButterKnife时,我们初始化一个Activity中的各个控件的代码:
[code]public class ExampleActivity extends Activity {
TextView title;
ImageView icon;
TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 通过findViewById进行视图查找,然后进行类型转换
title = (TextView) findViewById(R.id.title);
icon = (ImageView) findViewById(R.id.icon);
footer = (TextView) findViewById(R.id.footer);
}
}[/code] 在ExampleActivity函数的onCreate函数中,我们通常会对各个子视图进行初始化,这些代码看起来重复性很高,而且丑陋不堪,几乎都要对View进行强转,当一个布局中含有十个以上的View时,再加上为某些View添加上事件处理等,这部分的代码将占用很大的篇幅。ButterKnife就是为了简化这些工作而出现的,让开发人员专注在真正有用的代码上。使用ButterKnife之后我们的代码变成了这样:
[code]public class ExampleActivity extends Activity {
@InjectView(R.id.title) TextView title;
@InjectView(R.id.icon) ImageView icon;
@InjectView(R.id.footer) TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 将Activity注入ButterKnife
ButterKnife.inject(this);
}
}[/code] 当运行完onCreate函数之后Activity中的几个View就已经被初始化了。findViewById、强制转换等样板代码被去除了,代码变得更加简单,使得我们可以更专注在代码逻辑的编写上,整个类型也更易于维护。
那么ButterKnife的原理是什么呢?@InjectView又是什么?ButterKnife的inject函数又有什么作用?
这是因为ButterKnife使用了一种叫做编译时注解的技术(即APT), 代码在编译时会扫描AbstractProcessor的所有子类,并且调用这些子类的process函数,在这个函数就会将所有的代码元素传递进来。此时我们只需要在这个process函数中获取所有添加了某个注解的元素,然后对这些元素进行操作,使之能够满足我们的需求,这样我们就可以在编译期对源代码进行处理,例如生成新的类等。在运行时,我们通过一些接口对这些新生成的类进行调用以此完成我们的功能 。
说了这么多还是太抽象了,还是以小民的例子来为大家一一解除疑问吧。
小民自从知道ButterKnife之后也被它的魅力所吸引了,于是决定研究个究竟,经过一番搜索得知ButterKnife是基于编译时注解,然后通过APT生成辅助类,然后在运行时通过inject函数调用那些生成的辅助类来完成功能。小民决定自己写一个只支持View 的id注入的简版ButterKnife来深入学习,这个库被命名为SimpleDagger。
[code]首先小民建了一个注解类,代码如下 :[/code] [code]@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewInjector {
int value();
}[/code] 因为我们的这个注解只支持View的id注入,因此它的目标元素是字段,它只存在在class文件中,因为一旦过了编译期我们就不再需要它了。关于注解方面的基础知识我们不做过多讲解,对这方面不了解的同学可以先阅读相关书籍,例如《Java编程思想》、《Java核心技术》。
在添加AbstractProcessor 之前,为了使Eclipse支持 APT 需要一些配置,可以参考 injectdagger 。Android Studio要支持 APT则需要添加APT插件,有兴趣的同学可以自行搜索相关解决方案。
通过 APT 来生成辅助类型
添加这个注解之后,我们还需要在编译期对这个注解进行处理。上文说到,编译器会在编译时检测所有的AbstractProcessor并且调用它的process函数来让开发人员对代码元素进行处理。因此我们新建一个AbstractProcessor的子类,代码如下 :
[code]@SupportedAnnotationTypes("org.simple.injector.anno.*")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectorProcessor extends AbstractProcessor {
//所有注解处理器的列表
List mHandlers = new LinkedList();
//类型与字段的关联表,用于在写入Java文件时按类型来写不同的文件和字段
final Map<string, list> map = new HashMap<string, list>();
// 生成辅助累的Writer类
AdapterWriter mWriter;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 注册注解处理器
registerHandlers();
// 初始化代码生成器
mWriter = new DefaultJavaFileWriter(processingEnv);
}
// 注册处理器
private void registerHandlers() {
mHandlers.add(new ViewInjectHandler());
}
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
// 迭代所有的注解处理器,使得每个注解都有一个处理器,
for (AnnotationHandler handler : mHandlers) {
// 关联ProcessingEnvironment
handler.attachProcessingEnv(processingEnv);
// 解析注解相关的信息
map.putAll(handler.handleAnnotation(roundEnv));
}
// 将解析到的数据写入到具体的类型中
mWriter.generate(map);
return true;
}
// 代码省略
}[/code] 在ViewInjectorProcessor类的上面我们看到如下注解@SupportedAnnotationTypes(“org.simple.injector.anno.*”), 这个注解表明这个类只支持org.simple.injector.anno路径下的注解,我们的ViewInjector注解就是在这个包下。在该类的init函数中我们注册了一个注解处理器,也就是ViewInjectHandler类,该类实现了AnnotationHandler接口,该接口的声明如下 :
[code]// 注解处理接口
public interface AnnotationHandler {
// 关联ProcessingEnvironment
void attachProcessingEnv(ProcessingEnvironment processingEnv);
// 处理注解,将结果存储到Map中
Map<string, list> handleAnnotation(RoundEnvironment env);
}[/code] 该接口声明了两个函数,一个是关联ProcessingEnvironment,另一个是handleAnnotation函数,负责处理标识了ViewInjector注解的元素。小民的设计思路是定义一个AnnotationHandler接口,每个实现类处理一种类型的注解,例如ViewInjectHandler只处理ViewInject注解。下面我们看看ViewInjectHandler的核心代码 :
[code]public class ViewInjectHandler implements AnnotationHandler {
ProcessingEnvironment mProcessingEnv;
@Override
public void attachProcessingEnv(ProcessingEnvironment processingEnv) {
mProcessingEnv = processingEnv;
}
@Override
public Map<string, list> handleAnnotation(RoundEnvironment roundEnv) {
Map<string, list> annotationMap = new HashMap<string, list>();
// 1、获取使用ViewInjector注解的所有元素
Set elementSet = roundEnv.getElementsAnnotatedWith(ViewInjector.class);
for (Element element : elementSet) {
// 2、获取被注解的字段
VariableElement varElement = (VariableElement) element;
// 3、获取字段所在类型的完整路径名,比如一个TextView所在的Activity的完整路径,也就是变量的宿主类
String className = getParentClassName(varElement);
// 4、获取这个宿主类型的所有元素,例如某个Activity中的所有注解对象
List cacheElements = annotationMap.get(className);
if (cacheElements == null) {
cacheElements = new LinkedList();
}
// 将元素添加到该类型对应的字段列表中
cacheElements.add(varElement);
// 以宿主类的路径为key,所有字段列表为value,存入map.
// 这里是将所在字段按所属的类型进行分类
annotationMap.put(className, cacheElements);
}
return annotationMap;
}
// 代码省略
}[/code] 在handleAnnotation函数中小民获取了所有被ViewInject注解标识了的VariableElement元素,然后将这些元素按照宿主类进行分类存到一个map中,key就是宿主类的完整类路径,value就是这个宿主类中的所有被标识了ViewInject的VariableElement元素列表。例如将上述ExampleActivity的示例替换成小民的SimpleDagger,使用ViewInject注解标识中三个View,代码如下 :
[code]package com.simple.apt;
public class ExampleActivity extends Activity {
@ViewInject (R.id.title) TextView title;
@ViewInject (R.id.icon) ImageView icon;
@ViewInject (R.id.footer) TextView footer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
// 其他代码暂时省略
SimpleDagger.inject(this);
}
}[/code] 那么此时ExampleActivity的完整路径为com.simple.apt.ExampleActivity,这个完整路径我们可以通过VariableElement元素获取到,这些VariableElement就是代表了ExampleActiivty中的title、icon、footer三个对象。因此通过ViewInjectHandler的handleAnnotation处理之后我们的map中就含有了以com.simple.apt.ExampleActivity为key,以title、icon、footer三个成员变量对应的VariableElement列表为value的数据。
0 0
- ButterKnife 的实现原理
- 解析ButterKnife实现原理
- 解析ButterKnife实现原理
- ButterKnife的原理
- 深入理解编译注解(六)Butterknife的实现原理
- Butterknife原理
- Android ButterKnife 的实现思路
- 实现注入view,简单案例剖析butterknife原理
- 简单实现ButterKnife的注解功能
- 利用注解实现简单的ButterKnife
- butterKnife原理学习
- ButterKnife框架原理
- ButterKnife框架原理解剖
- ButterKnife框架原理
- ButterKnife框架原理
- ButterKnife原理解析
- Butterknife原理解析
- ButterKinfe原理,并简单仿照ButterKinfe定义自己的ButterKnife
- Linux Shell 文本处理工具集锦
- Memcache和Redis的详细理解与比较
- 多线程
- js中代码的执行机制
- windows上mysql在VS中的使用
- ButterKnife 的实现原理
- 单播、多播(组播)和广播的区别
- spring什么sh时候实例化bean
- 触摸屏getevent正常报数据,Android却无触点的bug
- 第一章 基本概念
- Android vector标签 PathData 画图超详解
- session详解
- 【Lijhtoj 1033 Generating Palindromes +最大公共子序列】
- 创建命名空间的几种方法