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.     @Bind(R.id.tv1)
  3.     TextView mTv1;
  4.     @Bind(R.id.tv2)
  5.     TextView mTv2;

  6.     @Override
  7.     protected void onCreate(Bundle savedInstanceState) {
  8.         super.onCreate(savedInstanceState);
  9.         setContentView(R.layout.activity_main);
  10.         ButterKnife.bind(this);
  11.         mTv1.setText("tv1已經得到了控件的索引");
  12.     }

  13.     @OnClick(R.id.tv2)
  14.     public void tv2OnClick() {
  15.         Toast.makeText(this,  "tv2被點擊了", Toast.LENGTH_SHORT).show();
  16.     }
複製代碼

這是一個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.   }
複製代碼
由上面代碼可以看出,最終需要調用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.   public void unbind(T target) {
  14.     target.mTv1 = null;
  15.     target.mTv2 = null;
  16.   }
  17. }
複製代碼

可以看出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.     @ViewById(R.id.myInput)
  4.     EditText myInput;

  5.     @ViewById(R.id.myTextView)
  6.     TextView textView;

  7.     @Click
  8.     void myButton() {
  9.         String name = myInput.getText().toString();
  10.         textView.setText("Hello "+name);
  11.     }
  12. }
複製代碼
再看下annotations生成的類。
  1. public final class MainActivity_
  2.     extends MainActivity
  3.     implements HasViews, OnViewChangedListener
  4. {

  5.     private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();

  6.     @Override
  7.     public void onCreate(Bundle savedInstanceState) {
  8.         OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
  9.         init_(savedInstanceState);
  10.         super.onCreate(savedInstanceState);
  11.         OnViewChangedNotifier.replaceNotifier(previousNotifier);
  12.         setContentView(layout.content_main);
  13.     }

  14.     private void init_(Bundle savedInstanceState) {
  15.         OnViewChangedNotifier.registerOnViewChangedListener(this);
  16.     }

  17.     @Override
  18.     public void setContentView(int layoutResID) {
  19.         super.setContentView(layoutResID);
  20.         onViewChangedNotifier_.notifyViewChanged(this);
  21.     }

  22.     @Override
  23.     public void setContentView(View view, LayoutParams params) {
  24.         super.setContentView(view, params);
  25.         onViewChangedNotifier_.notifyViewChanged(this);
  26.     }

  27.     @Override
  28.     public void setContentView(View view) {
  29.         super.setContentView(view);
  30.         onViewChangedNotifier_.notifyViewChanged(this);
  31.     }

  32.     public static MainActivity_.IntentBuilder_ intent(Context context) {
  33.         return new MainActivity_.IntentBuilder_(context);
  34.     }

  35.     public static MainActivity_.IntentBuilder_ intent(Fragment supportFragment) {
  36.         return new MainActivity_.IntentBuilder_(supportFragment);
  37.     }

  38.     @Override
  39.     public void onViewChanged(HasViews hasViews) {
  40.         myInput = ((EditText) hasViews.findViewById(id.myInput));
  41.         textView = ((TextView) hasViews.findViewById(id.myTextView));
  42.         {
  43.             View view = hasViews.findViewById(id.myButton);
  44.             if (view!= null) {
  45.                 view.setOnClickListener(new OnClickListener() {


  46.                     @Override
  47.                     public void onClick(View view) {
  48.                         MainActivity_.this.myButton();
  49.                     }

  50.                 }
  51.                 );
  52.             }
  53.         }
  54.     }
  55. }
複製代碼

方法調用鏈: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.         Class<?> handlerType = handler.getClass();

  3.         // inject ContentView
  4.         .......

  5.         // inject view
  6.         Field[] fields = handlerType.getDeclaredFields();
  7.         if (fields != null && fields.length > 0) {
  8.             for (Field field : fields) {
  9.                 ViewInject viewInject = field.getAnnotation(ViewInject.class);
  10.                 if (viewInject != null) {
  11.                     try {
  12.                         View view = finder.findViewById(viewInject.value(), viewInject.parentId());
  13.                         if (view != null) {
  14.                             field.setAccessible(true);
  15.                             field.set(handler, view);
  16.                         }
  17.                     } catch (Throwable e) {
  18.                         LogUtils.e(e.getMessage(), e);
  19.                     }
  20.                 } else {
  21.                     ResInject resInject = field.getAnnotation(ResInject.class);
  22.                     ...... // 跟viewInject類似
  23.                     } else {
  24.                         PreferenceInject preferenceInject = field.getAnnotation(PreferenceInject.class);
  25.                         ...... // 跟viewInject類似
  26.                     }
  27.                 }
  28.             }
  29.         }

  30.         // inject event
  31.         Method[] methods = handlerType.getDeclaredMethods();
  32.         if (methods != null && methods.length > 0) {
  33.             for (Method method : methods) {
  34.                 Annotation[] annotations = method.getDeclaredAnnotations();
  35.                 if (annotations != null && annotations.length > 0) {
  36.                     for (Annotation annotation : annotations) {
  37.                         Class<?> annType = annotation.annotationType();
  38.                         if (annType.getAnnotation(EventBase.class) != null) {
  39.                             method.setAccessible(true);
  40.                             try {
  41.                                 // ProGuard:-keep class * extends java.lang.annotation.Annotation { *; }
  42.                                 Method valueMethod = annType.getDeclaredMethod("value");
  43.                                 Method parentIdMethod = null;
  44.                                 try {
  45.                                     parentIdMethod = annType.getDeclaredMethod("parentId");
  46.                                 } catch (Throwable e) {
  47.                                 }
  48.                                 Object values = valueMethod.invoke(annotation);
  49.                                 Object parentIds = parentIdMethod == null ? null : parentIdMethod.invoke(annotation);
  50.                                 int parentIdsLen = parentIds == null ? 0 : Array.getLength(parentIds);
  51.                                 int len = Array.getLength(values);
  52.                                 for (int i = 0; i < len; i++) {
  53.                                     ViewInjectInfo info = new ViewInjectInfo();
  54.                                     info.value = Array.get(values, i);
  55.                                     info.parentId = parentIdsLen > i ? (Integer) Array.get(parentIds, i) : 0;
  56.                                     EventListenerManager.addEventMethod(finder, info, annotation, handler, method);
  57.                                 }
  58.                             } catch (Throwable e) {
  59.                                 LogUtils.e(e.getMessage(), e);
  60.                             }
  61.                         }
  62.                     }
  63.                 }
  64.             }
  65.         }
  66.     }
複製代碼
可以看到反射、反射到處在反射,雖然現在的反射速度也很快了,但是還是不能跟原生代碼相比,一旦註釋用的多了,這初始化速度會越來越慢。通過上面註釋處理的代碼可以看出,xutils支持的註釋目前主要有UI, 資源,事件,SharedPreference綁定。跟xutils一樣是運行時利用反射去解析註釋的框架還有afinal, roboguice等。
      市面上還有很多其他的註釋框架,但是萬變不離其宗,不是反射就是自動生成代碼。反射功能雖然強大,但是不可取,不僅會拖慢速度還會破話程序的封裝性。個人認爲生成代碼的方案比較好,所有的功能都在編譯時做了,並不會影響到用戶的體驗,唯一的缺點就是比反射難實現,不過我們程序不就是把難處留給自己,把快樂留給用戶麼!
     最後,對上面三種框架總結一下。

 


上面的難易,強弱,快慢都是相對他們三個自己來說的,比如AndroidAnnotations的接入評級是難,並不代表它的接入方式很難,只是相對ButterKnife和XUtils來說比他們難。如果只想使用UI綁定,資源綁定,事件綁定的功能,推薦使用ButterKnife。以上分析純屬個人觀點,僅供參考!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章