Android注解框架对比分析

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。以上分析纯属个人观点,仅供参考!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android中封装SDK时常用的注解总结

    前言 在工作中我们经常需要将功能模块封装成库供合作厂商调用, 如何写好一个健壮的Android Library有很多讲究,使用注解可以对SDK暴露给开发者的接口做出一些限制,从而尽可能地避免开发者错误地使用API. 下面我们介绍几种封装SDK时常用到的注解,需要的朋友们可以参考学习. 一.IntDef与StringDef 我们有时候会使用int常量或者String常量来代替枚举, 特别在你编写SDK的时候,你可以通过IntDef或者StringDef来限制接口可接受的参数. 比如,有一个 dis

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

  • Android 中的注解详细介绍

    注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies

  • Android AOP 注解详解及简单使用实例(三)

    Android  注解 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.简介 在Android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 可以更加方便快捷 安全有效的编写Java代码.谷歌出的support-annotations这个库 就是主要干这个的. 和反射一起 提供一些类似于spring 可配置的功能,方便简洁. 二

  • 深入分析安卓(Android)中的注解

    归纳而言,Android中的注解大概有以下好处 1.提高我们的开发效率 2.更早的发现程序的问题或者错误 3.更好的增加代码的描述能力 4.更加利于我们的一些规范约束 5.提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies { compile 'com.android.support:support-annotations:22.2.0' } 但是如果我们已经引入了 app

  • Android注解ButterKnife的基本使用

    ButterKnife的最新版本是8.4.0. 首先,需要导入ButterKnife的jar包. 在AndroidStudio中,File->Project Structure->Dependencies->Library dependency 搜索butterknife即可,第一个就是. 另外一种就是直接在build:grade(app)dependencies里添加: compile 'com.jakewharton:butterknife:8.4.0' annotationProc

  • Android 中的注解深入探究

    本文系GDG Android Meetup分享内容总结文章 注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个

  • Android注解使用之ButterKnife 8.0详解

    前言: App项目开发大部分时候还是以UI页面为主,这时我们需要调用大量的findViewById以及setOnClickListener等代码,控件的少的时候我们还能接受,控件多起来有时候就会有一种想砸键盘的冲动.所以这个时候我们想着可以借助注解的方式让我们从这种繁重的工作中脱离出来,也让代码变得更加简洁,便于维护,今天主要学习一下只专注View.Resource.Action注解框架ButterKnife. ButterKnife介绍 ButterKnife是一个专注于Android系统的V

  • Android AOP之注解处理解释器详解(二)

    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.提取Annotation信息 当开发者使用了Annotation修饰了类.方法.Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息.这些处理提取和处理Annotation的代码统称为APT(Annotation

  • Android注解框架对比分析

    Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解). 开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已.好在市面上有所谓的注解框架可以帮助开发者简化一些过程.比较流行的有butterknife

  • Android路由框架Router分析详解

    什么是路由?说简单点就是映射页面跳转关系的,当然它也包含跳转相关的一切功能. 路由框架的意义 Android系统已经给我们提供了api来做页面跳转,比如startActivity,为什么还需要路由框架呢?我们来简单分析下路由框架存在的意义: 在一些复杂的业务场景下(比如电商),灵活性比较强,很多功能都是运营人员动态配置的,比如下发一个活动页面,我们事先并不知道具体的目标页面,但如果事先做了约定,提前做好页面映射,便可以自由配置. 随着业务量的增长,客户端必然随之膨胀,开发人员的工作量越来越大,比

  • Android路由框架ARouter分析

    一.路由方案 原生的路由方案缺点: 显式:直接的类依赖,耦合严重 隐式:规则集中式管理,协作困难 Manifest扩展性较差 跳转过程无法控制 失败无法降级 ARouter的优势: 使用注解,实现了映射关系自动注册 与 分布式路由管理 编译期间处理注解,并生成映射文件,没有使用反射,不影响运行时性能 映射关系按组分类.多级管理,按需初始化 灵活的降级策略,每次跳转都会回调跳转结果,避免StartActivity()一旦失败将会抛出运营级异常 自定义拦截器,自定义拦截顺序,可以对路由进行拦截,比如

  • 深入浅析Java注解框架

    我们经常会在java代码里面看到:"@Override","@Target"等等样子的东西,这些是什么? 在java里面它们是"注解". 下面是百度百科的解释:java.lang.annotation.Retention可以在您定义Annotation型态时,指示编译器如何对待您的自定义 Annotation,预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯. 也就是说,注解

  • 测试框架JUnit VS TestNG对比分析

    目录 引言 单元测试 TestNG 和 JUnit 的区别 测试套件 注释 用例管理 分组测试 忽略测试 参数化 依赖测试 异常测试 超时测试 结论 引言 软件开发经历了许多阶段,如需求收集和分析.设计.软件开发.测试和发布.测试是 SDLC 不可或缺的一部分,单元测试是一种可靠的测试类型.像 JUnit 和 TestNG 这样优秀的单元测试框架已经成为主流选择,但是关于 TestNG 与 JUnit 的差异的争论一直存在. 单元测试 测试不是单一的活动,而是涵盖各种测试场景.它以不同的方式分类

  • Android getReadableDatabase() 和 getWritableDatabase()分析对比

    Android getReadableDatabase() 和 getWritableDatabase()分析对比 Android使用getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例.(getReadableDatabase()方法中会调用getWritableDatabase()方法) 其中getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库

  • 详解Android框架MVVM分析以及使用

    Android MVVM 分析以及使用 首先我们需要知道什么是MVVM,他的功能和优点,以及他的缺点. MVVM是Model-View-ViewModel的简写.它本质上就是MVC 的改进版.MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开.当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑.微软的WPF带来了新的技术体验,如Silverlight.音频.视频.3D.动画-

  • 爬虫框架 Feapder 和 Scrapy 的对比分析

    目录 一.scrapy 分析 1. 解析函数或数据入库出错,不会重试,会造成一定的数据丢失 2. 运行方式,需借助命令行,不方便调试 3. 入库 pipeline,不能批量入库 二.scrapy-redis 分析 1. redis 中的任务可读性不好 2. 取任务时直接弹出,会造成任务丢失 3. 去重耗内存 三.feapder 分析 四.三种爬虫简介 1. AirSpider 2. Spider 3. BatchSpider 五.feapder 项目结构 1. feapder 部署 六.采集效率

  • java协程框架quasar和kotlin中的协程对比分析

    目录 前言 快速体验 添加依赖 添加javaagent 线程VS协程 协程代码 多线程代码 协程完胜 后记 前言 早就听说Go语言开发的服务不用任何架构优化,就可以轻松实现百万级别的qps.这得益于Go语言级别的协程的处理效率.协程不同于线程,线程是操作系统级别的资源,创建线程,调度线程,销毁线程都是重量级别的操作.而且线程的资源有限,在java中大量的不加限制的创建线程非常容易将系统搞垮.接下来要分享的这个开源项目,正是解决了在java中只能使用多线程模型开发高并发应用的窘境,使得java也能

随机推荐