Android xUtils3源码解析之注解模块

农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述Android xUtils3源码解析之注解模块相关的知识,希望能为你提供帮助。
本文已授权微信公众号《非著名程序员》原创首发, 转载请务必注明出处。
xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块
二. Android xUtils3源码解析之图片模块
三. Android xUtils3源码解析之注解模块
四. Android xUtils3源码解析之数据库模块
初始化

public class BaseActivity extends AppCompatActivity {@ Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); x.view().inject(this); } }public class BaseFragment extends Fragment {@ Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return x.view().inject(this, inflater, container); } }

这里没有贴最开始的初始化x.Ext.init(this), 因为这行代码的作用是获取ApplicationContext, 而注解模块并不需要ApplicationContext。真正的初始化是在这里。实际上这里称作“初始化”有些不太合适, 因为xUtils3中View注解都是@ Retention(RetentionPolicy.RUNTIME)类型的, 运行时才是真正的初始化, x.view().inject(this)是解析注解的地方。注解一共就这俩部分, 先姑且这么称呼吧。下文以x.view().inject(this)为例进行分析, Fragment中和这个属于殊途同归, 不再赘述。
View注解 注解的作用只能是“标志”, 如果注解里定义的有属性, 那么还能获取属性具体的值。属性的值没有default值, 那么使用注解时此属性为必填项。反之亦反。我们先看下两个View注解ContentView和ViewInject的具体实现, 之后统一查看注解解析相关代码。
ContentView标签
@ Target(ElementType.TYPE) @ Retention(RetentionPolicy.RUNTIME) public @ interface ContentView { int value(); }

ContentView注解修饰的对象范围为TYPE( 用于描述类、接口或enum声明) , 保留的时间为RUNTIME( 运行时有效) , 此外还定义了一个属性value, 注意: 是属性, 不是方法。
ViewInject
@ Target(ElementType.FIELD) @ Retention(RetentionPolicy.RUNTIME) public @ interface ViewInject {int value(); /* parent view id */ int parentId() default 0; }

ViewInject注解修饰的对象范围为FIELD( 用于描述属性) , 保留的时间为RUNTIME( 运行时有效) 。
View注解解析 在Activity或者Fragment中首先要做的就是初始化xUtils3注解, 即x.view().inject(this)。前文也说过: 这个过程实际是View注解解析的过程。下面就以这一过程跟进。
x.view()
public final class x { public static ViewInjector view() { if (Ext.viewInjector = = null) { ViewInjectorImpl.registerInstance(); } return Ext.viewInjector; } }public final class ViewInjectorImpl implements ViewInjector { public static void registerInstance() { if (instance = = null) { synchronized (lock) { if (instance = = null) { instance = new ViewInjectorImpl(); } } } x.Ext.setViewInjector(instance); } }

获取ViewInjectorImpl唯一实例, 并赋值给ViewInjector对象。之后调用ViewInjectorImpl.inject()方法解析上面两个View注解。
ViewInjectorImpl.inject()
public final class ViewInjectorImpl implements ViewInjector {@ Override public void inject(Activity activity) { Class< ?> handlerType = activity.getClass(); try { // 获取ContentView标签, 主要是为了获取ContentView.value(), 即R.layout.xxx ContentView contentView = findContentView(handlerType); if (contentView != null) { // 获取R.layout.xxx int viewId = contentView.value(); if (viewId > 0) { // 获取setContentView()方法实例 Method setContentViewMethod = handlerType.getMethod(" setContentView" , int.class); // 反射调用setContentView(), 并设置R.layout.xxx setContentViewMethod.invoke(activity, viewId); } } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } // 遍历被注解的属性和方法 injectObject(activity, handlerType, new ViewFinder(activity)); } }

几乎每行都添加了注释, 应该比较清晰了, 这里还是大概说下吧。在反射setContentView ()之后, ContentView注解的作用就结束了, 毕竟ContentView注解的作用只有一个: 设置Activity/Fragment布局。
ViewInjectorImpl.injectObject()
public final class ViewInjectorImpl implements ViewInjector {private static final HashSet< Class< ?> > IGNORED = new HashSet< Class< ?> > (); static { IGNORED.add(Object.class); IGNORED.add(Activity.class); IGNORED.add(android.app.Fragment.class); try { IGNORED.add(Class.forName(" android.support.v4.app.Fragment" )); IGNORED.add(Class.forName(" android.support.v4.app.FragmentActivity" )); } catch (Throwable ignored) { } }private static void injectObject(Object handler, Class< ?> handlerType, ViewFinder finder) { if (handlerType = = null || IGNORED.contains(handlerType)) { return; } // 从父类到子类递归 injectObject(handler, handlerType.getSuperclass(), finder); // 获取class中所有属性 Field[] fields = handlerType.getDeclaredFields(); if (fields != null & & fields.length > 0) { for (Field field : fields) { // 获取字段类型 Class< ?> fieldType = field.getType(); if ( /* 不注入静态字段 */Modifier.isStatic(field.getModifiers()) || /* 不注入final字段 */Modifier.isFinal(field.getModifiers()) || /* 不注入基本类型字段 */fieldType.isPrimitive() || /* 不注入数组类型字段 */fieldType.isArray()) { continue; } // 字段是否被ViewInject注解修饰 ViewInject viewInject = field.getAnnotation(ViewInject.class); if (viewInject != null) { try { // 通过ViewFinder查找View View view = finder.findViewById(viewInject.value(), viewInject.parentId()); if (view != null) { // 暴力反射, 设置属性可使用 field.setAccessible(true); // 关联被ViewInject修饰的属性和View field.set(handler, view); } else { throw new RuntimeException(" Invalid @ ViewInject for " + handlerType.getSimpleName() + " ." + field.getName()); } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } } } } // end inject view// 方法注解Event的解析, 下文会讲 ... } }

因为Activity/Fragment可能还有BaseActivity/BaseFragment。所以injectObject()是个递归方法, 递归的出口在于最上面的判断, 及父类不等于系统的那几个类。finder.findViewById(id,pid)参数id为R.id.xxx,pid默认为0。在ViewFinder中查找View的代码如下:
/*package*/ final class ViewFinder {public View findViewById(int id, int pid) { View pView = null; if (pid > 0) { pView = this.findViewById(pid); }View view = null; if (pView != null) { view = pView.findViewById(id); } else { view = this.findViewById(id); } return view; }public View findViewById(int id) { if (view != null) return view.findViewById(id); if (activity != null) return activity.findViewById(id); return null; } }

还是通过activity.findViewById(id)来查找控件的。View注解的作用是代替我们写了findViewById这行代码, 一般用于敏捷开发。代价是增加了一次反射, 每个控件都会。而反射是比较牺牲性能的做法, 所以使用View注解算是有利有弊吧。
事件注解 Event
/** * 事件注解. * 被注解的方法必须具备以下形式: * 1. private 修饰 * 2. 返回值类型没有要求 * 3. 参数签名和type的接口要求的参数签名一致. */ @ Target(ElementType.METHOD) @ Retention(RetentionPolicy.RUNTIME) public @ interface Event {/** 控件的id集合, id小于1时不执行ui事件绑定. */ int[] value(); /** 控件的parent控件的id集合, 组合为(value[i], parentId[i] or 0). */ int[] parentId() default 0; /** 事件的listener, 默认为点击事件. */ Class< ?> type() default View.OnClickListener.class; /** 事件的setter方法名, 默认为set+ type#simpleName. */ String setter() default " " ; /** 如果type的接口类型提供多个方法, 需要使用此参数指定方法名. */ String method() default " " ; }

Event中的属性, 比View注解要多一些, 毕竟Event也需要findViewById过程, 并且还要处理参数, 事件等等。默认type属性为View.OnClickListener.class, 即点击事件。
public final class ViewInjectorImpl implements ViewInjector {private static void injectObject(Object handler, Class< ?> handlerType, ViewFinder finder) { // 获取类中所有的方法 Method[] methods = handlerType.getDeclaredMethods(); if (methods != null & & methods.length > 0) { for (Method method : methods) { // 方法是静态或者不是私有则验证不通过 if (Modifier.isStatic(method.getModifiers()) || !Modifier.isPrivate(method.getModifiers())) { continue; }//检查当前方法是否是event注解的方法 Event event = method.getAnnotation(Event.class); if (event != null) { try { // R.id.xxx数组( 可能多个控件点击事件共用同一个方法) int[] values = event.value(); int[] parentIds = event.parentId(); int parentIdsLen = parentIds = = null ? 0 : parentIds.length; //循环所有id, 生成ViewInfo并添加代理反射 for (int i = 0; i < values.length; i+ + ) { int value = values[i]; if (value > 0) { ViewInfo info = new ViewInfo(); info.value = value; info.parentId = parentIdsLen > i ? parentIds[i] : 0; // 设置可反射访问 method.setAccessible(true); EventListenerManager.addEventMethod(finder, info, event, handler, method); } } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } } } } // end inject event } }

这里主要是查找被Event注解修饰的方法, 之后设置可访问(method.setAccessible(true)), 看样子还是反射调用咯。
EventListenerManager.addEventMethod(finder, info, event, handler, method)
/*package*/ final class EventListenerManager {public static void addEventMethod( //根据页面或view holder生成的ViewFinder ViewFinder finder, //根据当前注解ID生成的ViewInfo ViewInfo info, //注解对象 Event event, //页面或view holder对象 Object handler, //当前注解方法 Method method) { try { // 查找指定控件 View view = finder.findViewByInfo(info); if (view != null) { // 注解中定义的接口, 比如Event注解默认的接口为View.OnClickListener Class< ?> listenerType = event.type(); // 默认为空, 注解接口对应的Set方法, 比如setOnClickListener方法 String listenerSetter = event.setter(); if (TextUtils.isEmpty(listenerSetter)) { // 拼接set方法名,例如: setOnClickListener listenerSetter = " set" + listenerType.getSimpleName(); } // 默认为" " String methodName = event.method(); boolean addNewMethod = false; DynamicHandler dynamicHandler = null; ... // 如果还没有注册此代理 if (!addNewMethod) { dynamicHandler = new DynamicHandler(handler); dynamicHandler.addMethod(methodName, method); // 生成的代理对象实例, 比如View.OnClickListener的实例对象 listener = Proxy.newProxyInstance( listenerType.getClassLoader(), new Class< ?> []{listenerType}, dynamicHandler); listenerCache.put(info, listenerType, listener); } // 获取set方法, 例如: setOnClickListener Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType); // 反射调用set方法。例如setOnClickListener(new OnClicklistener) setEventListenerMethod.invoke(view, listener); } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } }}

使用动态代理DynamicHandler实例化listenerType( 例如: new OnClickListener) ,之后通过反射设置事件( 例如点击事件, btn.setOnClickListener(new OnClickListener)) 。这么一套流程流程下来, 我惊讶的发现, 我们定义的方法好像完全没被调用! !
其实猫腻都在DynamicHandler这个动态代理中。注意一个细节, 在实例化DynamicHandler的时候穿递的是Activity/Fragment。然后调用dynamicHandler.addMethod(methodName, method)方法的时候, 将method( 当前注解方法) 传递进去了。完整类名有, 方法名字有。齐活儿~
DynamicHandler
public static class DynamicHandler implements InvocationHandler { // 存放代理对象, 比如Fragment或view holder private WeakReference< Object> handlerRef; // 存放代理方法 private final HashMap< String, Method> methodMap = new HashMap< String, Method> (1); private static long lastClickTime = 0; public DynamicHandler(Object handler) { this.handlerRef = new WeakReference< Object> (handler); }public void addMethod(String name, Method method) { methodMap.put(name, method); }public Object getHandler() { return handlerRef.get(); }@ Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object handler = handlerRef.get(); if (handler != null) { String eventMethod = method.getName(); method = methodMap.get(eventMethod); if (method = = null & & methodMap.size() = = 1) { for (Map.Entry< String, Method> entry : methodMap.entrySet()) { if (TextUtils.isEmpty(entry.getKey())) { method = entry.getValue(); } break; } }if (method != null) {if (AVOID_QUICK_EVENT_SET.contains(eventMethod)) { long timeSpan = System.currentTimeMillis() - lastClickTime; if (timeSpan < QUICK_EVENT_TIME_SPAN) { LogUtil.d(" onClick cancelled: " + timeSpan); return null; } lastClickTime = System.currentTimeMillis(); }try { return method.invoke(handler, args); } catch (Throwable ex) { throw new RuntimeException(" invoke method error:" + handler.getClass().getName() + " #" + method.getName(), ex); } } else { LogUtil.w(" method not impl: " + eventMethod + " (" + handler.getClass().getSimpleName() + " )" ); } } return null; } }

【Android xUtils3源码解析之注解模块】首先调用method = methodMap.get(eventMethod), 由key查找方法名, 之前我们传进来的是“”。以OnClickListener{ void onClick()}为例, 由onClick为key查找, 当然查找不到咯。然后遍历methodMap设置method为我们在Activity/Fragment中定义的方法名。if (AVOID_QUICK_EVENT_SET.contains(eventMethod))这行代码是防止快速双击的, 设置间隔为300ms, 最后通过反射调用在Activity/Fragment中特定被Event注解的方法。这里巧在没有调用OnClicklistener#onClick(), 而是在调用OnClicklistener#onClick()的时候, 真正调用的是我们在Activity/Fragment中定义的方法。体会一下这个过程。这里还需要注意一个地方, 因为return method.invoke(handler, args), 最后需要return返回值。所以在Activity/Fragment中定义方法的返回值, 必须要和目标方法( 例如: onClick()) 的返回值一样。

    推荐阅读