介绍
ButterKnife是一个专注于Android系统的View注入框架,以前总是要写很多findViewById来找到View对象,有了ButterKnife可以很轻松的省去这些步骤。是大神JakeWharton的力作,目前使用很广。最重要的一点,使用ButterKnife对性能基本没有损失,因为ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class。项目集成起来也是特别方便,使用起来也是特别简单。
使用
一、首先,如果你直接在你的项目app.gradle中引用依赖,你需要:
①在项目gradle-->dependencies节点下面,引入:
classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
②在app.gradle下面引入依赖:
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
完毕,然后就可以使用它。
二、第二种情况,如果你的项目是分模块的,而你的依赖恰好是在模块下引入的,则需要你:
①在相应的模块最上方添加:
apply plugin: 'com.jakewharton.butterknife'
②在此模块下添加依赖:
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
③在项目gradle-->dependencies节点下面,引入:
classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
完毕。
值得注意的是:如果你使用AndroidStudio 3.0 canary 8开发,你需要在app下的build.gradle文件下的defaultConfig节点下添加:
android {
......
defaultConfig {
......
//添加以下代码
javaCompileOptions {
annotationProcessorOptions
{
includeCompileClasspath = true
}
}
}
}
接下来就在代码中使用Butterknife框架吧:
MainActivity
public class MainActivity extends AppCompatActivity {
//2.注解获取对象
@BindView(R.id.btn_c)
Button btn_C;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.设置view之后,绑定当前view
ButterKnife.bind(this);
}
//绑定click事件
@OnClick(R.id.btn_c)
public void BtnClick(View view){
Toast.makeText(MainActivity.this,"butterknife test",Toast.LENGTH_SHORT).show();
}
}
①首先在Activity的onCreat方法中设置好View后,通过ButterKnife.bind(this)绑定View;
②然后再变量声明上面用BindView注解,传入布局文件对象的id即可获取对象;
③也可以注解点击事件,在声明函数上面使用OnClick注解,传入布局id即可,注意这里函数需要传入一个View类型的参数。
前言
不同于其他注解框架,Butterknife采用APT(Annotation Process Tool)编译时解析技术,避免程序运行时产生内耗影响程序流畅性。
普通注解框架采用运行时解析技术,其注解为RUNTIME即运行时生效,此类框架在程序运行时会产生大量的临时变量,有可能触发gc导致界面卡顿;而ButterKinfe是在编译程序时,将分析注解代码,在java文件生成class字节码文件的过程中,通过javapoet开源项目将代码写入class文件中,故而运行时不会产生影响;
实现
实现ButterKnife大致有三步:
1.通过apt技术将读取并扫描代码,获取代码元素;
2.通过代码元素,解析处理注解的代码;
3.通过javapoet填充代码,生成字节码文件;
那么看看它每一步的具体实现:
ButterKnifeProcessor类继承自AbstractProcessor类;
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
String sdk = env.getOptions().get(OPTION_SDK_INT);
if (sdk != null) {
try {
this.sdk = Integer.parseInt(sdk);
} catch (NumberFormatException e) {
env.getMessager()
.printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
+ sdk
+ "'. Falling back to API 1 support.");
}
}
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
首先,ButterKnifeProcessor类init方法中,做一些初始化操作,这里有三个重要变量:
① private Elements elementUtils:这里的Elements就相当于java源文件中的所有代码元素的集合;
② private Types typeUtils:源代码当中的类型信息;
③ private Filer filer:创建文件的工具;
在init方法里面已经为这三个变量初始化完成。
接下来我们看看ButterKnifeProcessor类process方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
process方法中,首先调用了findAndParseTargets方法扫描并解析源文件代码,得到源代码元素的集合,那么看看findAndParseTargets到底做了哪些工作:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
// Process each @BindArray element.
for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceArray(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindArray.class, e);
}
}
// Process each @BindBitmap element.
for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceBitmap(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindBitmap.class, e);
}
}
// Process each @BindBool element.
for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceBool(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindBool.class, e);
}
}
// Process each @BindColor element.
for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceColor(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindColor.class, e);
}
}
// Process each @BindDimen element.
for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceDimen(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindDimen.class, e);
}
}
// Process each @BindDrawable element.
for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceDrawable(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindDrawable.class, e);
}
}
// Process each @BindFloat element.
for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceFloat(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindFloat.class, e);
}
}
// Process each @BindInt element.
for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceInt(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindInt.class, e);
}
}
// Process each @BindString element.
for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceString(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindString.class, e);
}
}
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
// Process each @BindViews element.
for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindViews(element, builderMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindViews.class, e);
}
}
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, builderMap, erasedTargetNames);
}
// Associate superclass binders with their subclass binders. This is a queue-based tree walk
// which starts at the roots (superclasses) and walks to the leafs (subclasses).
Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
new ArrayDeque<>(builderMap.entrySet());
Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
while (!entries.isEmpty()) {
Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
TypeElement type = entry.getKey();
BindingSet.Builder builder = entry.getValue();
TypeElement parentType = findParentType(type, erasedTargetNames);
if (parentType == null) {
bindingMap.put(type, builder.build());
} else {
BindingSet parentBinding = bindingMap.get(parentType);
if (parentBinding != null) {
builder.setParent(parentBinding);
bindingMap.put(type, builder.build());
} else {
// Has a superclass binding but we haven't built it yet. Re-enqueue for later.
entries.addLast(entry);
}
}
}
return bindingMap;
}
findAndParseTargets方法一开始便调用了scanForRClasses(env)函数扫描并建立代码树(ClassTree),接下来遍历代码寻找并处理注解
{(BindArray.class)
(BindBitmap.class)
(BindBool.class)
(BindColor.class)
(BindDimen.class)
(BindDrawable.class)
(BindFloat.class)
(BindInt.class)
(BindString.class)
(BindView.class)
(BindViews.class)}
就拿BindView注解举个例子,处理BindView注解时,会调用parseBindView方法对BindView注解进行处理:
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
note(element, "@%s field with unresolved type (%s) "
+ "must elsewhere be generated as a View or interface. (%s.%s)",
BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(),
element.getSimpleName());
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
}
if (hasError) {
return;
}
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
BindingSet.Builder builder = builderMap.get(enclosingElement);
if (builder != null) {
String existingBindingName = builder.findExistingBindingName(getId(id));
if (existingBindingName != null) {
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBindingName,
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
}
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
builder.addField(getId(id), new FieldViewBinding(name, type, required));
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
可以看到找到bindView所对应的id后,将其加入builderMap当中保存。
回头我们看看process方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();
JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return false;
}
扫描处理完注解后,遍历BindingMap(存放的注解信息),然后通过brewJava函数(里面调用javapoet)将生成的语句通过filer写入class文件中。
至此butterknife的基本实现已经完成,基本流程如下图:
butterknife基本流程
有上图可知,在java源代码的编译过程中,apt会不断的解析java源文件声生成新的文件,然后我们的butterknife也会在编译过程中不断分析java源代码文件生成新文件,最后生成class字节码文件












网友评论