一.
二.
三.
四.
- 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);
- }
- }
这里没有贴最开始的初始化
,因为这行代码的作用是获取 ApplicationContext,而注解模块并不需要 ApplicationContext。真正的初始化是在这里。实际上这里称作 "初始化" 有些不太合适,因为 xUtils3 中 View 注解都是
- x.Ext.init(this)
类型的,运行时才是真正的初始化,
- @Retention(RetentionPolicy.RUNTIME)
是解析注解的地方。注解一共就这俩部分,先姑且这么称呼吧。下文以
- x.view().inject(this)
为例进行分析,Fragment 中和这个属于殊途同归,不再赘述。
- x.view().inject(this)
注解的作用只能是 "标志",如果注解里定义的有属性,那么还能获取属性具体的值。属性的值没有 default 值,那么使用注解时此属性为必填项。反之亦反。我们先看下两个 View 注解 ContentView 和 ViewInject 的具体实现,之后统一查看注解解析相关代码。
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ContentView {
- int value();
- }
ContentView 注解修饰的对象范围为 TYPE(用于描述类、接口或 enum 声明),保留的时间为 RUNTIME(运行时有效),此外还定义了一个属性 value,注意:是属性,不是方法。
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ViewInject {
- int value();
- /* parent view id */
- int parentId() default 0;
- }
ViewInject 注解修饰的对象范围为 FIELD(用于描述属性),保留的时间为 RUNTIME(运行时有效)。
在 Activity 或者 Fragment 中首先要做的就是初始化 xUtils3 注解,即
。前文也说过:这个过程实际是 View 注解解析的过程。下面就以这一过程跟进。
- x.view().inject(this)
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()
几乎每行都添加了注释,应该比较清晰了,这里还是大概说下吧。在反射 setContentView () 之后,ContentView 注解的作用就结束了,毕竟 ContentView 注解的作用只有一个:设置 Activity/Fragment 布局。
ViewInjectorImpl.injectObject()
- public final class ViewInjectorImpl implements ViewInjector {
- private static final HashSet > IGNORED = new HashSet > ();
- 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() 是个递归方法,递归的出口在于最上面的判断,及父类不等于系统的那几个类。
参数 id 为 R.id.xxx,pid 默认为 0。在 ViewFinder 中查找 View 的代码如下:
- finder.findViewById(id,pid)
- /*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;
- }
- }
还是通过
来查找控件的。View 注解的作用是代替我们写了 findViewById 这行代码,一般用于敏捷开发。代价是增加了一次反射,每个控件都会。而反射是比较牺牲性能的做法,所以使用 View 注解算是有利有弊吧。
- activity.findViewById(id)
- /**
- * 事件注解.
- * 被注解的方法必须具备以下形式:
- * 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。然后调用
方法的时候,将 method(当前注解方法)传递进去了。完整类名有,方法名字有。齐活儿~
- dynamicHandler.addMethod(methodName, method)
DynamicHandler
- public static class DynamicHandler implements InvocationHandler {
- // 存放代理对象,比如Fragment或view holder
- private WeakReference handlerRef;
- // 存放代理方法
- private final HashMap methodMap = new HashMap(1);
- private static long lastClickTime = 0;
- public DynamicHandler(Object handler) {
- this.handlerRef = new WeakReference(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 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;
- }
- }
首先调用
,由 key 查找方法名,之前我们传进来的是 ""。以
- method = methodMap.get(eventMethod)
为例,由 onClick 为 key 查找,当然查找不到咯。然后遍历 methodMap 设置 method 为我们在 Activity/Fragment 中定义的方法名。
- OnClickListener{ void onClick()}
这行代码是防止快速双击的,设置间隔为 300ms,最后通过反射调用在 Activity/Fragment 中特定被 Event 注解的方法。这里巧在没有调用 OnClicklistener#onClick(),而是在调用 OnClicklistener#onClick() 的时候,真正调用的是我们在 Activity/Fragment 中定义的方法。体会一下这个过程。这里还需要注意一个地方,因为
- if (AVOID_QUICK_EVENT_SET.contains(eventMethod))
,最后需要 return 返回值。所以在 Activity/Fragment 中定义方法的返回值,必须要和目标方法(例如:onClick())的返回值一样。
- return method.invoke(handler, args)
来源: http://www.bubuko.com/infodetail-2004931.html