butterknife android jar,android注解Butterknife的使用及代码分析

6f7a04488462

Paste_Image.png

大家好,今天老衲给大家带来的是Android另一款注解框架,ButterKnife的使用介绍及代码分析。

使用方式:

导入Butterknife的jar包。

不需要修改配置文件有木有,超级简单有木有,→_→

添加AndroidStudio插件(可选,需要依赖ButterKnife的jar包)

下载一个插件Android ButterKnife Zelezny来配合Butterknife自动生成View。

6f7a04488462

JfQ73eI.gif

注意,需要绑定的View或者资源的声明必须是public,不能是private或者static,至于原因,我们会在下面的分析中讲到

Butterknife常用的注解:

Butterknife支持Activity,Fragment,View,Dialog,ViewHolder类内部的View绑定

@Bind

TextView mTextView//最常用的注解,用来绑定View,避免findViewById,也可以用在ViewHolder里,必须是public

@Bind({ R.id.first_name, R.id.middle_name, R.id.last_name })

List nameViews//绑定多个view,只能用List不能用ArrayList

@OnClick(R.id.submit)

public void submit(View view) {...}//绑定点击事件,支持多个id绑定同一个方法

@OnItemSelected(R.id.list_view)

void onItemSelected(int position) {...}//selected事件

@OnItemClick(R.id.example_list)

void onItemClick(int position) {...}//itemClick事件

@OnFocusChange(R.id.example)

void onFocusChanged(boolean focused){...}//焦点改变监听

@OnItemLongClick(R.id.example_list)

boolean onItemLongClick(int position){...}//长按监听

@OnPageChange(R.id.example_pager)

void onPageSelected(int position){...}//Viewpager切换监听

@OnTextChanged(R.id.example)

void onTextChanged(CharSequence text)//内容改变监听

@BindInt//用来绑定Integer类型的resource ID

@BindString//用来绑定string.xml里的字符串

@BindDrawable//用来绑定图片

@BindColor//用来绑定颜色

@BindDimen//用来绑定dimens

ButterKnife所提供的注解的着重点放在了View的处理上,减少了开发时View处理的时间,相对于AndroidAnnotation来说,功能较为的单一。

Butterknife的实现流程

概述:Butterknife在编译时刻利用APT分析程序代码,扫描每一个有注解的类,找出类中带有注解的字段

@Bind生成ViewBinding的子类,

监听类的生成ListenerBinding的子类,

通过Java的FilerAPI生成多个包含注入代码的辅助类,程序中调用ButterKnife.bind()方法时加载这些辅助类实现依赖注入。

1.绑定XML布局

为Android的View绑定ID或者方法

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//绑定activity

ButterKnife.bind(this);

text.setText("HELLO , WORLD");

}

2.ButterKnife.bind内部的处理

static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {

//当前的activity,dialog,fragment,View等

Class> targetClass = target.getClass();

try {

//1.创建一个类的实例 , 加入到缓存,并返回该实例

ViewBinder viewBinder = findViewBinderForClass(targetClass);

//2.调用了实例中的bind方法

viewBinder.bind(finder, target, source);

} catch (Exception e) {

throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);

}}

2.1.创建类的实例。

举个栗子:

现在我在activity中执行ButterKnife.Bind方法。他会去寻找XXXActivity$$ViewBinder这个类,通过反射加载并创建类的实例对象。至于为什么要找这个类,这个会在下面分析。我们先来看下接下来的操作

private static ViewBinder findViewBinderForClass(Class> cls)

throws IllegalAccessException, InstantiationException {

ViewBinder viewBinder = BINDERS.get(cls);

if (viewBinder != null) {

//首先从缓存中判断是否存在对应的ViewBinder,如果有直接返回

if (debug) Log.d(TAG, "HIT: Cached in view binder map.");

return viewBinder;

}

String clsName = cls.getName();

if (clsName.startsWith("android.") || clsName.startsWith("java.")) {

//ButterKnife提供的注解只支持在应用程序使用,如果扫描的是framework层的类,则返回NOP_VIEW_BINDER

if (debug)

Log.d(TAG, "MISS: Reached framework class. Abandoning search.");

return NOP_VIEW_BINDER;

}

try {

//根据反射原理,构造了类的实例,其实就是各种监听的生成类,详情如下图

Class> viewBindingClass = Class.forName(clsName + "$$ViewBinder");

viewBinder = (ViewBinder) viewBindingClass.newInstance();

if (debug)

Log.d(TAG, "HIT: Loaded view binder class.");

} catch (ClassNotFoundException e) {

if (debug)

Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());

//如果查找不到就递归去查找SuperClass$$ViewBinder这个类。

viewBinder = findViewBinderForClass(cls.getSuperclass());

}

//添加到缓存中

BINDERS.put(cls, viewBinder);

return viewBinder;

}

2.2.执行类的实例的Bind方法。这里我们通过反编译APK逆推下

先介绍下ViewBinder,他是一个接口。所有使用过ButterKnife中@Bind注解的类都会生成一个中间工具类用来实现View绑定或者数据绑定的业务逻辑,这个中间工具类会继承原始类(Activity,Fragment)并实现ViewBinder接口,ViewBinder中有一个抽象方法bind,这便是ButterKnife需要来替我们实现的用来绑定数据方法。如下

温馨提示:ButterKnife官网也有类似的介绍,如果下面的看不懂可以去官网查看。

/**

* Created by alexshaw on 16-3-26.

*/

public class MainActivity extends AppCompatActivity

{

@Bind(R.id.text)

TextView mText;

@Bind(R.id.confirm)

Button mConfirm;

@Bind(R.id.cancle)

Button mCancle;

@BindString(R.string.hello)

String mString;

@Bind(R.id.icon)

ImageView mIcon;

@BindDrawable(R.drawable.ic_launcher)

Drawable drawable;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ButterKnife.bind(this);

mText.setText(mString);

mIcon.setImageDrawable(drawable);

}

@OnClick(R.id.confirm)

public void onConfirm(View view) { mText.setText("确认"); }

@OnClick(R.id.cancle)

public void onCancle(View view) { mText.setText("取消"); }}

如上代码如果编译成APK,生成的新代码会如何呢?如下图

首先是MAinActivity(因篇幅问题,这里只展示重要的代码,见谅)

6f7a04488462

Paste_Image.png

这样我们就可以看到onCreate中调用了ButterKnife.Bind方法。我们之前分析了Bind方法的业务逻辑,无非两步

查找XXX$$ViewBinder并创建实例

调用实例的bind方法

此时我们在看下ButterKnife帮助我们生成的中间类

6f7a04488462

Paste_Image.png

接下来就是分析MainActivity$$ViewBinder这个类了

6f7a04488462

Paste_Image.png

简单介绍下bind方法的参数,第二个参数和第三个参数是相同的,都是Activity或者Fragment等类的实例,第一个参数为Finder,他是一个枚举类型,提供了一系列用来查找指定ID的View或者资源的方法。

这时再来看bind方法,我们就可以理解了。paramT就是被绑定的类(Activity,Fragment)它内部的属性通过Find这个类来查找并赋值。在这里我们就可以解释文章开始时遗留下的问题了。为什么@Bind注解绑定的变量必须声明为public。

原理我们介绍了,接下来。我们就要介绍下实现流程了。

Butterknife的处理流程

但凡涉及到注解的处理,都需要找AbstractProcessor或者是其实现类 , Butterknife的实现入口是ButterKnifeProcessor类,该类继承自AbstractProcessor并重写了process方法来处理添加了注解的Java类。

1. 注解处理的入口

@Override

public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {

//存储BindingClass的集合,重点是findAndParseTargets方法

Map targetClassMap = findAndParseTargets(env);

for (Map.Entry entry : targetClassMap.entrySet()) {

TypeElement typeElement = entry.getKey();

BindingClass bindingClass = entry.getValue();

try {

bindingClass.brewJava().writeTo(filer);

} catch (IOException e) {

error(typeElement, "Unable to write view binder for type %s: %s", typeElement,

e.getMessage());

}

}

return true;

}

findAndParseTargets方法是用来查找出所有注解标注过的元素他的业务逻辑如下:(因代码是在太长,这里只显示主要代码,其他业务逻辑用注释表示)

private MapfindAndParseTargets(RoundEnvironment

env) {

// 遍历每一个@Bind元素

for (Element element : env.getElementsAnnotatedWith(Bind.class)) {

// 校验是否合法

if (!SuperficialValidation.validateElement(element))

continue;

try {

parseBind(element, targetClassMap, erasedTargetNames);

} catch (Exception e) {

logParsingError(element, Bind.class, e);

}

}

// 遍历每一个监听的注解元素

for (Class extends Annotation> listener : LISTENERS) {

findAndParseListener(env, listener, targetClassMap, erasedTargetNames);

}

// 遍历 @BindArray 元素.

// 遍历 @BindBool 元素.

// 遍历 @BindColor 元素.

// 遍历 @BindDimen 元素.

// 遍历 @BindDrawable 元素.

// 遍历 @BindInt 元素.

// 遍历 @BindString 元素.

// 遍历 @Unbinder 元素.

}

2. 接下来就是解析Bind注解元素和各种监听类注解元素的逻辑处理

/**

* @Bind注解的处理

*

* @param element

* @param targetClassMap

* @param erasedTargetNames

*/

//遍历每一个被注解标注过得属性或方法

private void parseBindOne(Element element, Map targetClassMap,

Set erasedTargetNames) {

//第一步:进行校验...判断目标字段的定义类型是否是View的子类型或者是一个接口类型,

检查目标字段的可访问性,是否只绑定了一个ID,balabala...

//第二步:判断集合中是否有元素对应的bindingClass

BindingClass bindingClass = targetClassMap.get(enclosingElement);

if (bindingClass != null) {//如果有:判断是否重复绑定

//通过ID拿到ViewBinders

ViewBindings viewBindings = bindingClass.getViewBinding(id);

if (viewBindings != null) {

Iterator iterator = viewBindings.getFieldBindings().iterator();

if (iterator.hasNext()) {

FieldViewBinding existingBinding = iterator.next();

error(...);

return;

}

}

} else {//如果没:创建一个BindingClass并添加到集合里去

bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);

}

String name = element.getSimpleName().toString();

TypeName type = TypeName.get(elementType);

boolean required = isFieldRequired(element);

FieldViewBinding binding = new FieldViewBinding(name, type, required);

bindingClass.addField(id, binding);

// Add the type-erased version to the valid binding targets set.

erasedTargetNames.add(enclosingElement);

/**

* 解析监听注解

* @param annotationClass

* @param element

* @param targetClassMap

* @param erasedTargetNames

* @throws Exception

*/

private void parseListenerAnnotation(Class extends Annotation> annotationClass, Element element,

Map targetClassMap, Set erasedTargetNames)

throws Exception {

//第一步做各种判断...

//返回类型是否是int[]

//是否是private或者static

//ID是否重复

//监听注解类是否存在

//ID是否合法

//balabala太长了..愁死我了...

//重点!!!!!!!!!!!!

Parameter[] parameters = Parameter.NONE;

if (!methodParameters.isEmpty()) {

//方法的参数的判断及处理,后面会用到

}

//通过将方法名称,参数,required组合成一个MethodViewBinding并传入bindingClass,

//最后将bindClass传入集合中

MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);

BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);

for (int id : ids) {

if (!bindingClass.addMethod(id, listener, method, binding)) {

error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)",

id, enclosingElement.getQualifiedName(), element.getSimpleName());

return;

}

}

erasedTargetNames.add(enclosingElement);

/**

* 创建targetClass,即XXX$$ViewBinder类,这里确定了XXX的名字类型等信息

* @param targetClassMap

* @param enclosingElement

* @return

*/

private BindingClass getOrCreateTargetClass(Map targetClassMap, TypeElement enclosingElement) {

BindingClass bindingClass = targetClassMap.get(enclosingElement);

if (bindingClass == null) {

//类或者接口的全名

String targetType = enclosingElement.getQualifiedName().toString();

// BINDING_CLASS_SUFFIX = "$$ViewBinder",新生成的类的名字

String classPackage = getPackageName(enclosingElement);

//包名,类名,完全限定名称

String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX;

bindingClass = new BindingClass(classPackage, className, targetType);

targetClassMap.put(enclosingElement, bindingClass); }

return bindingClass;

}

3. 属性和方法都解析完了,targetClassMap集合中的数据也齐全了。剩下的就是依靠数据生成新的中间类文件了。

@Override

public boolean process(Set extends TypeElement> elements, RoundEnvironment env) {

Map targetClassMap = findAndParseTargets(env);

//遍历map并生成文件

for (Map.Entry entry : targetClassMap.entrySet()) {

bindingClass.brewJava().writeTo(filer);

}

return true;

}

来瞅一眼文件生成的方法。bindingClass.brewJava()方法

JavaFile brewJava() {

//添加类名 public class XXX extends XXX implement XXX

TypeSpec.Builder result = TypeSpec.classBuilder(className)

.addModifiers(PUBLIC)

.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));

if (parentViewBinder != null) {

result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),

TypeVariableName.get("T")));

} else {

result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));

}

//添加方法

result.addMethod(createBindMethod());

if (hasUnbinder()) {

// Create unbinding class.

result.addType(createUnbinderClass());

//sss

createUnbinderInternalAccessMethods(result);

}

return JavaFile.builder(classPackage, result.build())

.addFileComment("Generated code from Butter Knife. Do not modify!")

.build();

}

addMethod方法中的代码

/**

* 创建最终反编译得到的class文件中的bind方法的代码

*

*/

private MethodSpec createBindMethod() {

MethodSpec.Builder result = MethodSpec.methodBuilder("bind")//方法名

.addAnnotation(Override.class)//添加override注解

.addModifiers(PUBLIC) //public

.addParameter(FINDER, "finder", FINAL)//param1

.addParameter(TypeVariableName.get("T"), "target", FINAL)//param2

.addParameter(Object.class, "source");//param3

balabala...

//查看viewIDMap集合是否有数据,即是否有绑定ID的View,如果有则加进去

if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {

result.addStatement("$T view", VIEW);

//添加bind方法里的代码:“view = finder.findOptionalView(source, $L, null)”

for (ViewBindings bindings : viewIdMap.values()) {

addViewBindings(result, bindings);

}

//绑定集合

for (Map.Entry entry : collectionBindings.entrySet()) {

emitCollectionBinding(result, entry.getKey(), entry.getValue());

}

}

balabala....

最后将生成的这些字符串写到java文件中。然后就可以编译成class文件了。。

最后使用FilerAPI创建辅助类文件,BindingClass的brewJava()方法根据模型“酝酿”Java代码,之后使用Java IO流把代码写入文件。

AndroidAnnotation(AA)与ButterKnife的比较,

AA的分析如果没看的话建议先读一下老衲的上一篇AA注解的介绍与流程分析

首先从功能上来说,AA提供的注解数量远多于ButterKnife,功能也是无所不包(View的绑定,线程,监听,动画,balabala...)而ButterKnife仅仅提供针对View的注解。

其次从两类框架的实现流程上来说,AA在一开始就已经生成了新的代码XXXActivity_,后续的执行都是依赖于新的代码。生成的方法和代码量较多。ButterKnife在编译时也是会生成新的中间工具类,代码量相对于AA来说略少,但是新增了类文件。并且,在运行时,需要通过一点点反射的技术来实现整体的逻辑。

第三,从上手成都上来说,AA的前期工作略麻烦一些,并且后期需要手动修改类名(XXX的后面加上下划线)ButterKnife则需要在类中添加ButterKnife.Bind方法来使用绑定功能。AA稍微麻烦一丢丢。

好了,ButterKnife的使用介绍,流程分析以及它和AA之间的比较已经写完,如果有什么意见或者不对的地方,请大家指正。

接下来想要给带来的是第三款注解框架Dagger2的使用及流程分析,以及现阶段流行的图片加载资源库的分析,但是,由于老衲智商最近一直没上线→_→,导致Dagger2的环境配了一个多星期还没配好。所以推出时间可能会略晚,希望大家见谅。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值