Android:注解框架对比

转载自http://blog.csdn.net/p892848153/article/details/50433279

 

       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。

  1. public class MainActivity extends Activity {
  2.  
  3.     @Bind(R.id.tv1)
  4.     TextView mTv1;
  5.     @Bind(R.id.tv2)
  6.     TextView mTv2;
  7.  
  8.     @Override
  9.     protected void onCreate(Bundle savedInstanceState) {
  10.         super.onCreate(savedInstanceState);
  11.         setContentView(R.layout.activity_main);
  12.         ButterKnife.bind(this);
  13.         mTv1.setText("tv1已经得到了控件的索引");
  14.     }
  15.  
  16.     @OnClick(R.id.tv2)
  17.     public void tv2OnClick() {
  18.         Toast.makeText(this,  "tv2被点击了", Toast.LENGTH_SHORT).show();
  19.     }
复制代码


这是一个View绑定的例子,你需要在成员变量上注解需要绑定的控件id,然后再调用ButterKnife.bind(Activity target)方法对带注解的成员变量进行赋值。ok, 看下ButterKnife.bind()是如何工作的。

  1. /**
  2.    * Bind annotated fields and methods in the specified {@link Activity}. The current content
  3.    * view is used as the view root.
  4.    *
  5.    * @param target Target activity for view binding.
  6.    */
  7.   public static void bind(Activity target) {
  8.     bind(target, target, Finder.ACTIVITY);
  9.   }
  10.  
复制代码

由上面代码可以看出,最终需要调用bind(Object target, Object source, Finder finder)方法。

  1. static void bind(Object target, Object source, Finder finder) {
  2.     Class<?> targetClass = target.getClass();
  3.     try {
  4.       if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
  5.       ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
  6.       if (viewBinder != null) {
  7.         viewBinder.bind(finder, target, source);
  8.       }
  9.     } catch (Exception e) {
  10.       throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
  11.     }
  12.   }
复制代码


这个方法就是寻找或者生成对应的 ViewBinder对象,然后调用该对象的bind(Finder finder, T target, Object source)方法为被注解的变量赋值。先看下findViewBinderForClass(Class<?> cls)方法。

  1. private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
  2.       throws IllegalAccessException, InstantiationException {
  3.     ViewBinder<Object> viewBinder = BINDERS.get(cls);
  4.     if (viewBinder != null) {
  5.       if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
  6.       return viewBinder;
  7.     }
  8.     String clsName = cls.getName();
  9.     if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
  10.       if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
  11.       return NOP_VIEW_BINDER;
  12.     }
  13.     try {
  14.       Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
  15.       //noinspection unchecked
  16.       viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
  17.       if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
  18.     } catch (ClassNotFoundException e) {
  19.       if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
  20.       viewBinder = findViewBinderForClass(cls.getSuperclass());
  21.     }
  22.     BINDERS.put(cls, viewBinder);
  23.     return viewBinder;
  24.   }
复制代码

 

 

可以看出是利用反射生成一个ViewBinder对象出来,而且还带有缓存,也就是说,同一个class多次调用只会反射一次。反射虽然比原生代码慢一些,但是如果只有一次反射的话,对性能的影响完全可以忽略不计。那现在的问题就是这个反射生成的ViewBinder是什么东西, 它的bind(Finder finder, T target, Object source) 方法是如何为被注解的变量赋值的?
       上面说过Butterknife框架是编译时注解,一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别~~~。在程序编译时, Butterknife会跟据所有的注解生成对应的代码比如说上面的MainActivity类,Butterknife会生成

 

 

  1. public class MainActivity$ViewBinder<T extends MainActivity> implements ButterKnife.ViewBinder<T> {
  2.   
  3.   public void bind(ButterKnife.Finder finder, final T target, Object source) {
  4.     target.mTv1 = ((TextView)finder.castView((View)finder.findRequiredView(source, 2131492971, "field 'mTv1'"), 2131492971, "field 'mTv1'"));
  5.     View localView = (View)finder.findRequiredView(source, 2131492972, "field 'mTv2' and method 'tv2OnClick'");
  6.     target.mTv2 = ((TextView)finder.castView(localView, 2131492972, "field 'mTv2'"));
  7.     localView.setOnClickListener(new DebouncingOnClickListener() {
  8.       public void doClick(View paramAnonymousView) {
  9.         target.tv2OnClick();
  10.       }
  11.     });
  12.   }
  13.  
  14.   public void unbind(T target) {
  15.     target.mTv1 = null;
  16.     target.mTv2 = null;
  17.   }
  18. }
复制代码

可以看出Bind注解到最后就是调用生成的代码来findViewById然后给其赋值的,事件就是给view设置一个默认的事件,然后里面调用你注解的那个方法。所以在性能上,ButterKnife并不会影响到app。 Butterknife 自动生产的代码也不多,不会对程序的包大小有什么影响。

 

AndroidAnnotations框架分析
      再来分析下著名的Annotations框架。该框架的原理跟Butterknife一样,都是在编译时生成代码,不过annotations并不是生成代码供对应的类调用去给带注解的变量、方法赋值,而是直接生成一个继承带注解的类,这个类里面有对变量赋值,对注解方法调用的代码。运行时,直接运行的是annotations生成的类,而不是我们写的类。说了这么多是不是不太明白,没关系,demo来了!先看下我们写的类。

  1. @EActivity(R.layout.content_main)
  2. public class MainActivity extends Activity {
  3.  
  4.     @ViewById(R.id.myInput)
  5.     EditText myInput;
  6.  
  7.     @ViewById(R.id.myTextView)
  8.     TextView textView;
  9.  
  10.     @Click
  11.     void myButton() {
  12.         String name = myInput.getText().toString();
  13.         textView.setText("Hello "+name);
  14.     }
  15. }
复制代码

 

再看下annotations生成的类。

 

  1. public final class MainActivity_
  2.     extends MainActivity
  3.     implements HasViews, OnViewChangedListener
  4. {
  5.  
  6.     private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();
  7.  
  8.     @Override
  9.     public void onCreate(Bundle savedInstanceState) {
  10.         OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
  11.         init_(savedInstanceState);
  12.         super.onCreate(savedInstanceState);
  13.         OnViewChangedNotifier.replaceNotifier(previousNotifier);
  14.         setContentView(layout.content_main);
  15.     }
  16.  
  17.     private void init_(Bundle savedInstanceState) {
  18.         OnViewChangedNotifier.registerOnViewChangedListener(this);
  19.     }
  20.  
  21.     @Override
  22.     public void setContentView(int layoutResID) {
  23.         super.setContentView(layoutResID);
  24.         onViewChangedNotifier_.notifyViewChanged(this);
  25.     }
  26.  
  27.     @Override
  28.     public void setContentView(View view, LayoutParams params) {
  29.         super.setContentView(view, params);
  30.         onViewChangedNotifier_.notifyViewChanged(this);
  31.     }
  32.  
  33.     @Override
  34.     public void setContentView(View view) {
  35.         super.setContentView(view);
  36.         onViewChangedNotifier_.notifyViewChanged(this);
  37.     }
  38.  
  39.     public static MainActivity_.IntentBuilder_ intent(Context context) {
  40.         return new MainActivity_.IntentBuilder_(context);
  41.     }
  42.  
  43.     public static MainActivity_.IntentBuilder_ intent(Fragment supportFragment) {
  44.         return new MainActivity_.IntentBuilder_(supportFragment);
  45.     }
  46.  
  47.     @Override
  48.     public void onViewChanged(HasViews hasViews) {
  49.         myInput = ((EditText) hasViews.findViewById(id.myInput));
  50.         textView = ((TextView) hasViews.findViewById(id.myTextView));
  51.         {
  52.             View view = hasViews.findViewById(id.myButton);
  53.             if (view!= null) {
  54.                 view.setOnClickListener(new OnClickListener() {
  55.  
  56.  
  57.                     @Override
  58.                     public void onClick(View view) {
  59.                         MainActivity_.this.myButton();
  60.                     }
  61.  
  62.                 }
  63.                 );
  64.             }
  65.         }
  66.     }
  67. }
复制代码


方法调用链: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,拿源码说话。

 

  1. private static void injectObject(Object handler, ViewFinder finder) {
  2.  
  3.         Class<?> handlerType = handler.getClass();
  4.  
  5.         // inject ContentView
  6.         .......
  7.  
  8.         // inject view
  9.         Field[] fields = handlerType.getDeclaredFields();
  10.         if (fields != null && fields.length > 0) {
  11.             for (Field field : fields) {
  12.                 ViewInject viewInject = field.getAnnotation(ViewInject.class);
  13.                 if (viewInject != null) {
  14.                     try {
  15.                         View view = finder.findViewById(viewInject.value(), viewInject.parentId());
  16.                         if (view != null) {
  17.                             field.setAccessible(true);
  18.                             field.set(handler, view);
  19.                         }
  20.                     } catch (Throwable e) {
  21.                         LogUtils.e(e.getMessage(), e);
  22.                     }
  23.                 } else {
  24.                     ResInject resInject = field.getAnnotation(ResInject.class);
  25.                     ...... // 跟viewInject类似
  26.                     } else {
  27.                         PreferenceInject preferenceInject = field.getAnnotation(PreferenceInject.class);
  28.                         ...... // 跟viewInject类似
  29.                     }
  30.                 }
  31.             }
  32.         }
  33.  
  34.         // inject event
  35.         Method[] methods = handlerType.getDeclaredMethods();
  36.         if (methods != null && methods.length > 0) {
  37.             for (Method method : methods) {
  38.                 Annotation[] annotations = method.getDeclaredAnnotations();
  39.                 if (annotations != null && annotations.length > 0) {
  40.                     for (Annotation annotation : annotations) {
  41.                         Class<?> annType = annotation.annotationType();
  42.                         if (annType.getAnnotation(EventBase.class) != null) {
  43.                             method.setAccessible(true);
  44.                             try {
  45.                                 // ProGuard:-keep class * extends java.lang.annotation.Annotation { *; }
  46.                                 Method valueMethod = annType.getDeclaredMethod("value");
  47.                                 Method parentIdMethod = null;
  48.                                 try {
  49.                                     parentIdMethod = annType.getDeclaredMethod("parentId");
  50.                                 } catch (Throwable e) {
  51.                                 }
  52.                                 Object values = valueMethod.invoke(annotation);
  53.                                 Object parentIds = parentIdMethod == null ? null : parentIdMethod.invoke(annotation);
  54.                                 int parentIdsLen = parentIds == null ? 0 : Array.getLength(parentIds);
  55.                                 int len = Array.getLength(values);
  56.                                 for (int i = 0; i < len; i++) {
  57.                                     ViewInjectInfo info = new ViewInjectInfo();
  58.                                     info.value = Array.get(values, i);
  59.                                     info.parentId = parentIdsLen > i ? (Integer) Array.get(parentIds, i) : 0;
  60.                                     EventListenerManager.addEventMethod(finder, info, annotation, handler, method);
  61.                                 }
  62.                             } catch (Throwable e) {
  63.                                 LogUtils.e(e.getMessage(), e);
  64.                             }
  65.                         }
  66.                     }
  67.                 }
  68.             }
  69.         }
  70.     }
复制代码

 

可以看到反射、反射到处在反射,虽然现在的反射速度也很快了,但是还是不能跟原生代码相比,一旦注释用的多了,这初始化速度会越来越慢。通过上面注释处理的代码可以看出,xutils支持的注释目前主要有UI, 资源,事件,SharedPreference绑定。跟xutils一样是运行时利用反射去解析注释的框架还有afinal, roboguice等。
      市面上还有很多其他的注释框架,但是万变不离其宗,不是反射就是自动生成代码。反射功能虽然强大,但是不可取,不仅会拖慢速度还会破话程序的封装性。个人认为生成代码的方案比较好,所有的功能都在编译时做了,并不会影响到用户的体验,唯一的缺点就是比反射难实现,不过我们程序不就是把难处留给自己,把快乐留给用户么!
     最后,对上面三种框架总结一下。

 

 

 

上面的难易,强弱,快慢都是相对他们三个自己来说的,比如AndroidAnnotations的接入评级是难,并不代表它的接入方式很难,只是相对ButterKnife和XUtils来说比他们难。如果只想使用UI绑定,资源绑定,事件绑定的功能,推荐使用ButterKnife。以上分析纯属个人观点,仅供参考!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值