农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述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())
的返回值一样。推荐阅读
- Android学习资源网站大全
- Android xUtils3源码解析之数据库模块
- 值得你关注的Android8.0(Android O)上的重要变化
- Google App Engine10年,支持更多你喜欢的编程语言
- Android studio 2.3安装遇到的问题
- PhoneGap 获得APP的VersionName
- 学习嵌入式开发板的Android平台体系结构和源码结构
- Android利用Fiddler进行网络数据抓包
- h5开发app之在线生成二维码