Android xUtils3源码解析之注解模块
本文已授权微信公众号《非著名程序员》原创首发,转载请务必注明出处。
xUtils3源码解析系列
一. Android xUtils3源码解析之网络模块
二. Android xUtils3源码解析之图片模块
三. Android xUtils3源码解析之注解模块
四. Android xUtils3源码解析之数据库模块
初始化
public class BaseActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);x.view().inject(this);}
}public class BaseFragment extends Fragment {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {return x.view().inject(this, inflater, container);}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
这里没有贴最开始的初始化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();
}
- 1
- 2
- 3
- 4
- 5
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
ContentView注解修饰的对象范围为TYPE(用于描述类、接口或enum声明),保留的时间为RUNTIME(运行时有效),此外还定义了一个属性value,注意:是属性,不是方法。
ViewInject
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {int value();/* parent view id */int parentId() default 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
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);}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
获取ViewInjectorImpl唯一实例,并赋值给ViewInjector对象。之后调用ViewInjectorImpl.inject()方法解析上面两个View注解。
ViewInjectorImpl.inject()
public final class ViewInjectorImpl implements ViewInjector {@Overridepublic void inject(Activity activity) {Class<?> handlerType = activity.getClass();try {// 获取ContentView标签,主要是为了获取ContentView.value(),即R.layout.xxxContentView contentView = findContentView(handlerType);if (contentView != null) {// 获取R.layout.xxxint viewId = contentView.value();if (viewId > 0) {// 获取setContentView()方法实例Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);// 反射调用setContentView(),并设置R.layout.xxxsetContentViewMethod.invoke(activity, viewId);}}} catch (Throwable ex) {LogUtil.e(ex.getMessage(), ex);}// 遍历被注解的属性和方法injectObject(activity, handlerType, new ViewFinder(activity));}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
几乎每行都添加了注释,应该比较清晰了,这里还是大概说下吧。在反射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查找ViewView view = finder.findViewById(viewInject.value(), viewInject.parentId());if (view != null) {// 暴力反射,设置属性可使用field.setAccessible(true);// 关联被ViewInject修饰的属性和Viewfield.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的解析,下文会讲...}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
因为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;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
还是通过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 "";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
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}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
这里主要是查找被Event注解修饰的方法,之后设置可访问(method.setAccessible(true)),看样子还是反射调用咯。
EventListenerManager.addEventMethod(finder, info, event, handler, method)
/*package*/ final class EventListenerManager {public static void addEventMethod(//根据页面或view holder生成的ViewFinderViewFinder finder,//根据当前注解ID生成的ViewInfoViewInfo info,//注解对象Event event,//页面或view holder对象Object handler,//当前注解方法Method method) {try {// 查找指定控件View view = finder.findViewByInfo(info);if (view != null) {// 注解中定义的接口,比如Event注解默认的接口为View.OnClickListenerClass<?> listenerType = event.type();// 默认为空,注解接口对应的Set方法,比如setOnClickListener方法String listenerSetter = event.setter();if (TextUtils.isEmpty(listenerSetter)) {// 拼接set方法名,例如:setOnClickListenerlistenerSetter = "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方法,例如:setOnClickListenerMethod setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);// 反射调用set方法。例如setOnClickListener(new OnClicklistener)setEventListenerMethod.invoke(view, listener);}} catch (Throwable ex) {LogUtil.e(ex.getMessage(), ex);}}}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
使用动态代理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 holderprivate 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();}@Overridepublic 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;}}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
![](http://static.blog.csdn.net/images/save_snippets.png)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
首先调用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())的返回值一样。
Android xUtils3源码解析之注解模块相关推荐
- Android xUtils3源码解析之图片模块
本文已授权微信公众号<非著名程序员>原创首发,转载请务必注明出处. xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3 ...
- Android xUtils3源码解析之数据库模块
本文已授权微信公众号<非著名程序员>原创首发,转载请务必注明出处. xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3 ...
- Android Lifecycle源码解析(一)
Android Lifecycle源码解析(一) 首先我们看HomeActivity中我们添加到一行代码 public class HomeActivity extends AppCompatActi ...
- 【Android】Android Broadcast源码解析
Android Broadcast源码解析 一.静态广播的注册 静态广播是通过PackageManagerService在启动的时候扫描已安装的应用去注册的. 在PackageManagerServi ...
- Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)
我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...
- Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)
在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...
- 【Android】Android Parcelable 源码解析
Android Parcelable 源码解析 大家都知道,要想在Intent里面传递一些非基本类型的数据,有两种方式,一种实现Parcelable,另一种是实现Serializable接口.今天先不 ...
- [Android] Handler源码解析 (Java层)
之前写过一篇文章,概述了Android应用程序消息处理机制.本文在此文基础上,在源码级别上展开进行概述 简单用例 Handler的使用方法如下所示: Handler myHandler = new H ...
- android sdk 源码解析
AndroidSdkSourceAnalysis:https://github.com/LittleFriendsGroup/AndroidSdkSourceAnalysis 第一期 Class 分析 ...
最新文章
- Struts2漏洞的前因后果
- python中哈希是什么意思_在python中向量化特征哈希
- php 解析 saml协议,解出SAMLRequest的代码示例
- mysql flask-login_Flask web模板六–Flask-Login完成登录验证
- Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues
- Scikit-Learn 学得如何?程序员不容错过十大实用功能来袭
- python装饰器打印函数执行时间_使用python装饰器计算函数运行时间的实例
- Vijos 1041题:神风堂人数
- oracle字典在线查字手写,在线字典手写输入
- hibernate中持久化类的编写规则和主键生成策略
- android信鸽推送通知栏,【信鸽推送】点击推送通知后,默认会从程序Launcher进入,返回时会回到主界面的问题...
- Unity Mecanim动画的实现(一):基本程序
- python做淘宝客_python 做淘宝客程序(2)
- 写给这批≥30岁的测试工程师 。
- Android 实现仿微信朋友圈九宫格图片+NineGridView+ImageWatcher(图片查看:1.预览,2.拖动,3.放大,4.左右滑动,5.长按保存到手机)的功能
- HTML-浮动(float)
- 51nod 1298 圆与三角形
- oracle查询当天的数据(当年,当月,当日)
- 支持他们的应用程序突袭Cloudberry狂潮
- 【科普】十大科研好用软件