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 uncheckedviewBinder = (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) 方法是如何为被注解的变量赋值的?
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 自动生产的代码也不多,不会对程序的包大小有什么影响。
@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框架里面一个反射都没有,没错这个框架没有用到反射,没有初始化,所有的工作在编译时都做了,不会对我们的程序造成任何速度上的影响。
private static void injectObject(Object handler, ViewFinder finder) {Class<?> handlerType = handler.getClass();// inject ContentView.......// inject viewField[] 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 eventMethod[] 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。以上分析纯属个人观点,仅供参考!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。