Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上。然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解)。

开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已。好在市面上有所谓的注解框架可以帮助开发者简化一些过程。比较流行的有butterknife, annotations, xutils, afinal, roboguice等等。今天我们就来对比一下这些注解框架。
ButterKnife框架分析
首先看下Butterknife,来自Jakewharton大神的力作,特点是接入简单,依赖一个库就好了。另外在Android Studio上还有提供一个插件,自动生成注解与类属性。
Butterknife目前支持的注解有: View绑定(Bind),资源绑定(BindBool, BindColor, BindDimen, BindDrawble, BindInt, BindString),事件绑定(OnCheckedChanged, OnClick, OnEditorAction, OnFocusChange, OnItemClick, OnItemLongClick, OnItemSelected, OnLongClick, OnPageChange, OnTextChanged, OnTouch)。
Butterknife的原理是运行时注解。先来看下一个demo。
public class MainActivity extends Activity {
@Bind(R.id.tv1)
TextView mTv1;
@Bind(R.id.tv2)
TextView mTv2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mTv1.setText("tv1已经得到了控件的索引");
}
@OnClick(R.id.tv2)
public void tv2OnClick() {
Toast.makeText(this, "tv2被点击了", Toast.LENGTH_SHORT).show();
}
这是一个View绑定的例子,你需要在成员变量上注解需要绑定的控件id,然后再调用ButterKnife.bind(Activity target)方法对带注解的成员变量进行赋值。ok, 看下ButterKnife.bind()是如何工作的。
/**
* Bind annotated fields and methods in the specified {@link Activity}. The current content
* view is used as the view root.
*
* @param target Target activity for view binding.
*/
public static void bind(Activity target) {
bind(target, target, Finder.ACTIVITY);
}
由上面代码可以看出,最终需要调用bind(Object target, Object source, Finder finder)方法。
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<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);
}
}
这个方法就是寻找或者生成对应的 ViewBinder对象,然后调用该对象的bind(Finder finder, T target, Object source)方法为被注解的变量赋值。先看下findViewBinderForClass(Class<?> cls)方法。
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
throws IllegalAccessException, InstantiationException {
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
String clsName = cls.getName();
if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
try {
Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
//noinspection unchecked
viewBinder = (ViewBinder<Object>) 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());
viewBinder = findViewBinderForClass(cls.getSuperclass());
}
BINDERS.put(cls, viewBinder);
return viewBinder;
}
可以看出是利用反射生成一个ViewBinder对象出来,而且还带有缓存,也就是说,同一个class多次调用只会反射一次。反射虽然比原生代码慢一些,但是如果只有一次反射的话,对性能的影响完全可以忽略不计。那现在的问题就是这个反射生成的ViewBinder是什么东西, 它的bind(Finder finder, T target, Object source) 方法是如何为被注解的变量赋值的?
上面说过Butterknife框架是编译时注解,一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别~~~。在程序编译时, Butterknife会跟据所有的注解生成对应的代码比如说上面的MainActivity类,Butterknife会生成
public class MainActivity$ViewBinder<T extends MainActivity> implements ButterKnife.ViewBinder<T> {
public void bind(ButterKnife.Finder finder, final T target, Object source) {
target.mTv1 = ((TextView)finder.castView((View)finder.findRequiredView(source, 2131492971, "field 'mTv1'"), 2131492971, "field 'mTv1'"));
View localView = (View)finder.findRequiredView(source, 2131492972, "field 'mTv2' and method 'tv2OnClick'");
target.mTv2 = ((TextView)finder.castView(localView, 2131492972, "field 'mTv2'"));
localView.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View paramAnonymousView) {
target.tv2OnClick();
}
});
}
public void unbind(T target) {
target.mTv1 = null;
target.mTv2 = null;
}
}
可以看出Bind注解到最后就是调用生成的代码来findViewById然后给其赋值的,事件就是给view设置一个默认的事件,然后里面调用你注解的那个方法。所以在性能上,ButterKnife并不会影响到app。 Butterknife 自动生产的代码也不多,不会对程序的包大小有什么影响。
AndroidAnnotations框架分析
再来分析下著名的Annotations框架。该框架的原理跟Butterknife一样,都是在编译时生成代码,不过annotations并不是生成代码供对应的类调用去给带注解的变量、方法赋值,而是直接生成一个继承带注解的类,这个类里面有对变量赋值,对注解方法调用的代码。运行时,直接运行的是annotations生成的类,而不是我们写的类。说了这么多是不是不太明白,没关系,demo来了!先看下我们写的类。
@EActivity(R.layout.content_main)
public class MainActivity extends Activity {
@ViewById(R.id.myInput)
EditText myInput;
@ViewById(R.id.myTextView)
TextView textView;
@Click
void myButton() {
String name = myInput.getText().toString();
textView.setText("Hello "+name);
}
}
再看下annotations生成的类。
public final class MainActivity_
extends MainActivity
implements HasViews, OnViewChangedListener
{
private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();
@Override
public void onCreate(Bundle savedInstanceState) {
OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
init_(savedInstanceState);
super.onCreate(savedInstanceState);
OnViewChangedNotifier.replaceNotifier(previousNotifier);
setContentView(layout.content_main);
}
private void init_(Bundle savedInstanceState) {
OnViewChangedNotifier.registerOnViewChangedListener(this);
}
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
onViewChangedNotifier_.notifyViewChanged(this);
}
@Override
public void setContentView(View view, LayoutParams params) {
super.setContentView(view, params);
onViewChangedNotifier_.notifyViewChanged(this);
}
@Override
public void setContentView(View view) {
super.setContentView(view);
onViewChangedNotifier_.notifyViewChanged(this);
}
public static MainActivity_.IntentBuilder_ intent(Context context) {
return new MainActivity_.IntentBuilder_(context);
}
public static MainActivity_.IntentBuilder_ intent(Fragment supportFragment) {
return new MainActivity_.IntentBuilder_(supportFragment);
}
@Override
public void onViewChanged(HasViews hasViews) {
myInput = ((EditText) hasViews.findViewById(id.myInput));
textView = ((TextView) hasViews.findViewById(id.myTextView));
{
View view = hasViews.findViewById(id.myButton);
if (view!= null) {
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
MainActivity_.this.myButton();
}
}
);
}
}
}
}
方法调用链:onCreate(Bundle saveInstanceState) ----> setContentView() ----> onViewChangedNotifier_.notifyViewChanged(),而onViewChanagedNotifier_.notifyViewChanged()方法最终会调用onViewChanged(HasViews hasViews)方法,在此方法中有对变量赋值,事件方法设置的代码,注意看自动生成的类的名字,发现规律了吧,就是我们写的类的名字后面加上一个'_'符号,现在知道为什么用Annotations框架,我们的AndroidManifest.xml中对Activity 的配置,Activity的名字要多加一个'_'符号了吧。因为真正加载的是AndroidAnnotations生成的代码。写到这里大家发现没,annotations框架里面一个反射都没有,没错这个框架没有用到反射,没有初始化,所有的工作在编译时都做了,不会对我们的程序造成任何速度上的影响。
那Annotations支持哪些注解呢?既然Annotations性能上跟Butterknife差不多,那功能呢?在这里翻译一下官网的Features.
1)、依赖注入:注入views, extras, 系统服务,资源,...
2)、简化线程模式:在方法上添加注释来制定该方法是运行在UI线程还是子线程。
3)、事件绑定:在方法上添加注释来制定该方法处理那些views的那个事件。
4)、REST client:创建一个client的接口,AndroidAnnotations会生成实现代码,这是关于网络方面的。
5)、清晰明了:AndroidAnnotations会在编译时自动生成对应子类,我们可以查看相应的子类来了解程序是怎么运行的。
XUtils框架分析
xutils框架是我们现在在用的框架,那我们就来分析一下他的注解功能。xutils的使用方式跟Butterknife一样,都是在成员变量,方法上添加注释,然后调用一个方法(xutils是ViewUtils.inject()方法)对成员变量赋值、事件方法设置到view上。不同的是,Butterknife是调用自动生成的代码来赋值,而xutils是通过反射来实现的。ok,拿源码说话。
private static void injectObject(Object handler, ViewFinder finder) {
Class<?> handlerType = handler.getClass();
// inject ContentView
.......
// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);
field.set(handler, view);
}
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
}
} else {
ResInject resInject = field.getAnnotation(ResInject.class);
...... // 跟viewInject类似
} else {
PreferenceInject preferenceInject = field.getAnnotation(PreferenceInject.class);
...... // 跟viewInject类似
}
}
}
}
// inject event
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
Annotation[] annotations = method.getDeclaredAnnotations();
if (annotations != null && annotations.length > 0) {
for (Annotation annotation : annotations) {
Class<?> annType = annotation.annotationType();
if (annType.getAnnotation(EventBase.class) != null) {
method.setAccessible(true);
try {
// ProGuard:-keep class * extends java.lang.annotation.Annotation { *; }
Method valueMethod = annType.getDeclaredMethod("value");
Method parentIdMethod = null;
try {
parentIdMethod = annType.getDeclaredMethod("parentId");
} catch (Throwable e) {
}
Object values = valueMethod.invoke(annotation);
Object parentIds = parentIdMethod == null ? null : parentIdMethod.invoke(annotation);
int parentIdsLen = parentIds == null ? 0 : Array.getLength(parentIds);
int len = Array.getLength(values);
for (int i = 0; i < len; i++) {
ViewInjectInfo info = new ViewInjectInfo();
info.value = Array.get(values, i);
info.parentId = parentIdsLen > i ? (Integer) Array.get(parentIds, i) : 0;
EventListenerManager.addEventMethod(finder, info, annotation, handler, method);
}
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
}
}
}
}
}
}
}
可以看到反射、反射到处在反射,虽然现在的反射速度也很快了,但是还是不能跟原生代码相比,一旦注释用的多了,这初始化速度会越来越慢。通过上面注释处理的代码可以看出,xutils支持的注释目前主要有UI, 资源,事件,SharedPreference绑定。跟xutils一样是运行时利用反射去解析注释的框架还有afinal, roboguice等。
市面上还有很多其他的注释框架,但是万变不离其宗,不是反射就是自动生成代码。反射功能虽然强大,但是不可取,不仅会拖慢速度还会破话程序的封装性。个人认为生成代码的方案比较好,所有的功能都在编译时做了,并不会影响到用户的体验,唯一的缺点就是比反射难实现,不过我们程序不就是把难处留给自己,把快乐留给用户么!
最后,对上面三种框架总结一下。
上面的难易,强弱,快慢都是相对他们三个自己来说的,比如AndroidAnnotations的接入评级是难,并不代表它的接入方式很难,只是相对ButterKnife和XUtils来说比他们难。如果只想使用UI绑定,资源绑定,事件绑定的功能,推荐使用ButterKnife。以上分析纯属个人观点,仅供参考!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# 注解框架
# 自定义Android注解系列教程之注解变量
# Android基于注解的6.0权限动态请求框架详解
# Android 反射注解与动态代理综合使用详解
# Android注解使用之ButterKnife 8.0详解
# Android中封装SDK时常用的注解总结
# Android AOP 注解详解及简单使用实例(三)
# Android AOP之注解处理解释器详解(二)
# Android AOP注解Annotation详解(一)
# Android注解ButterKnife的基本使用
# Android 中的注解详细介绍
# Android 中的注解深入探究
# 深入分析安卓(Android)中的注解
# Android注解基础介绍快速入门与解读
# 绑定
# 的是
# 自动生成
# 可以看出
# 都是
# 子类
# 会在
# 这类
# 影响到
# 就来
# 先看
# 这是
# 有什么
# 是在
# 来了
# 万变不离其宗
# 在这里
# 你可以
# 都在
# 很难
相关文章:
建站主机选购指南与交易推荐:核心配置解析
建站OpenVZ教程与优化策略:配置指南与性能提升
网站制作需要会哪些技术,建立一个网站要花费多少?
小程序网站制作需要准备什么资料,如何制作小程序?
网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?
如何快速登录WAP自助建站平台?
网站制作壁纸教程视频,电脑壁纸网站?
如何在云主机上快速搭建多站点网站?
北京制作网站的公司排名,北京三快科技有限公司是做什么?北京三快科技?
上海网站制作网站建设公司,建筑电工证网上查询系统入口?
弹幕视频网站制作教程下载,弹幕视频网站是什么意思?
如何高效完成独享虚拟主机建站?
建站之星展会模板:智能建站与自助搭建高效解决方案
赚钱网站制作软件,建一个网站怎样才能赚钱?是如何盈利的?
长沙企业网站制作哪家好,长沙水业集团官方网站?
如何快速生成专业多端适配建站电话?
如何基于云服务器快速搭建网站及云盘系统?
建站主机与虚拟主机有何区别?如何选择最优方案?
Android自定义listview布局实现上拉加载下拉刷新功能
Bpmn 2.0的XML文件怎么画流程图
惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?
江苏网站制作公司有哪些,江苏书法考级官方网站?
网站按钮制作软件,如何实现网页中按钮的自动点击?
网站制作费用多少钱,一个网站的运营,需要哪些费用?
智能起名网站制作软件有哪些,制作logo的软件?
广州网站设计制作一条龙,广州巨网网络科技有限公司是干什么的?
b2c电商网站制作流程,b2c水平综合的电商平台?
如何确保西部建站助手FTP传输的安全性?
电视网站制作tvbox接口,云海电视怎样自定义添加电视源?
定制建站价位费用解析与套餐推荐全攻略
如何通过建站之星自助学习解决操作问题?
深入理解Android中的xmlns:tools属性
公司网站设计制作厂家,怎么创建自己的一个网站?
免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
建站之星安装后如何配置SEO及设计样式?
专业网站制作服务公司,有哪些网站可以免费发布招聘信息?
如何用y主机助手快速搭建网站?
C#怎么使用委托和事件 C# delegate与event编程方法
网站好制作吗知乎,网站开发好学吗?有什么技巧?
广州网站制作的公司,现在专门做网站的公司有没有哪几家是比较好的,性价比高,模板也多的?
网站建设设计制作营销公司南阳,如何策划设计和建设网站?
如何挑选高效建站主机与优质域名?
建站之星五站合一营销型网站搭建攻略,流量入口全覆盖优化指南
高防服务器如何保障网站安全无虞?
油猴 教程,油猴搜脚本为什么会网页无法显示?
青岛网站建设如何选择本地服务器?
北京网站制作网页,网站升级改版需要多久?
如何在IIS7中新建站点?详细步骤解析
建站之星安装步骤有哪些常见问题?
*请认真填写需求信息,我们会在24小时内与您取得联系。