上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)。

本篇博客将带大家实现View的事件的注入。

1、目标效果

上篇博客,我们的事件的代码是这么写的:

[java] view plaincopy
  1. package com.zhy.zhy_xutils_test;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.view.View.OnClickListener;
  6. import android.widget.Button;
  7. import android.widget.Toast;
  8. import com.zhy.ioc.view.ViewInjectUtils;
  9. import com.zhy.ioc.view.annotation.ContentView;
  10. import com.zhy.ioc.view.annotation.ViewInject;
  11. @ContentView(value = R.layout.activity_main)
  12. public class MainActivity extends Activity implements OnClickListener
  13. {
  14. @ViewInject(R.id.id_btn)
  15. private Button mBtn1;
  16. @ViewInject(R.id.id_btn02)
  17. private Button mBtn2;
  18. @Override
  19. protected void onCreate(Bundle savedInstanceState)
  20. {
  21. super.onCreate(savedInstanceState);
  22. ViewInjectUtils.inject(this);
  23. mBtn1.setOnClickListener(this);
  24. mBtn2.setOnClickListener(this);
  25. }
  26. @Override
  27. public void onClick(View v)
  28. {
  29. switch (v.getId())
  30. {
  31. case R.id.id_btn:
  32. Toast.makeText(MainActivity.this, "Why do you click me ?",
  33. Toast.LENGTH_SHORT).show();
  34. break;
  35. case R.id.id_btn02:
  36. Toast.makeText(MainActivity.this, "I am sleeping !!!",
  37. Toast.LENGTH_SHORT).show();
  38. break;
  39. }
  40. }
  41. }

光有View的注入能行么,我们写View的目的,很多是用来交互的,得可以点击神马的吧。摒弃传统的神马,setOnClickListener,然后实现匿名类或者别的方式神马的,我们改变为:

[java] view plaincopy
  1. package com.zhy.zhy_xutils_test;
  2. import android.view.View;
  3. import android.widget.Button;
  4. import android.widget.Toast;
  5. import com.zhy.ioc.view.annotation.ContentView;
  6. import com.zhy.ioc.view.annotation.OnClick;
  7. import com.zhy.ioc.view.annotation.ViewInject;
  8. @ContentView(value = R.layout.activity_main)
  9. public class MainActivity extends BaseActivity
  10. {
  11. @ViewInject(R.id.id_btn)
  12. private Button mBtn1;
  13. @ViewInject(R.id.id_btn02)
  14. private Button mBtn2;
  15. @OnClick({ R.id.id_btn, R.id.id_btn02 })
  16. public void clickBtnInvoked(View view)
  17. {
  18. switch (view.getId())
  19. {
  20. case R.id.id_btn:
  21. Toast.makeText(this, "Inject Btn01 !", Toast.LENGTH_SHORT).show();
  22. break;
  23. case R.id.id_btn02:
  24. Toast.makeText(this, "Inject Btn02 !", Toast.LENGTH_SHORT).show();
  25. break;
  26. }
  27. }
  28. }

直接通过在Activity中的任何一个方法上,添加注解,完成1个或多个控件的事件的注入。这里我把onCreate搬到了BaseActivity中,里面调用了ViewInjectUtils.inject(this);

2、实现

1、注解文件

[java] view plaincopy
  1. package com.zhy.ioc.view.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.ANNOTATION_TYPE)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface EventBase
  9. {
  10. Class<?> listenerType();
  11. String listenerSetter();
  12. String methodName();
  13. }
[java] view plaincopy
  1. package com.zhy.ioc.view.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. import android.view.View;
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @EventBase(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
  10. public @interface OnClick
  11. {
  12. int[] value();
  13. }

EventBase主要用于给OnClick这类注解上添加注解,毕竟事件很多,并且设置监听器的名称,监听器的类型,调用的方法名都是固定的,对应上面代码的:

listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick"

Onclick是用于写在Activity的某个方法上的:

[java] view plaincopy
  1. @OnClick({ R.id.id_btn, R.id.id_btn02 })
  2. public void clickBtnInvoked(View view)

如果你还记得,上篇博客我们的ViewInjectUtils.inject(this);里面已经有了两个方法,本篇多了一个:

[java] view plaincopy
  1. public static void inject(Activity activity)
  2. {
  3. injectContentView(activity);
  4. injectViews(activity);
  5. injectEvents(activity);
  6. }

2、injectEvents

[java] view plaincopy
  1. /**
  2. * 注入所有的事件
  3. *
  4. * @param activity
  5. */
  6. private static void injectEvents(Activity activity)
  7. {
  8. Class<? extends Activity> clazz = activity.getClass();
  9. Method[] methods = clazz.getMethods();
  10. //遍历所有的方法
  11. for (Method method : methods)
  12. {
  13. Annotation[] annotations = method.getAnnotations();
  14. //拿到方法上的所有的注解
  15. for (Annotation annotation : annotations)
  16. {
  17. Class<? extends Annotation> annotationType = annotation
  18. .annotationType();
  19. //拿到注解上的注解
  20. EventBase eventBaseAnnotation = annotationType
  21. .getAnnotation(EventBase.class);
  22. //如果设置为EventBase
  23. if (eventBaseAnnotation != null)
  24. {
  25. //取出设置监听器的名称,监听器的类型,调用的方法名
  26. String listenerSetter = eventBaseAnnotation
  27. .listenerSetter();
  28. Class<?> listenerType = eventBaseAnnotation.listenerType();
  29. String methodName = eventBaseAnnotation.methodName();
  30. try
  31. {
  32. //拿到Onclick注解中的value方法
  33. Method aMethod = annotationType
  34. .getDeclaredMethod("value");
  35. //取出所有的viewId
  36. int[] viewIds = (int[]) aMethod
  37. .invoke(annotation, null);
  38. //通过InvocationHandler设置代理
  39. DynamicHandler handler = new DynamicHandler(activity);
  40. handler.addMethod(methodName, method);
  41. Object listener = Proxy.newProxyInstance(
  42. listenerType.getClassLoader(),
  43. new Class<?>[] { listenerType }, handler);
  44. //遍历所有的View,设置事件
  45. for (int viewId : viewIds)
  46. {
  47. View view = activity.findViewById(viewId);
  48. Method setEventListenerMethod = view.getClass()
  49. .getMethod(listenerSetter, listenerType);
  50. setEventListenerMethod.invoke(view, listener);
  51. }
  52. } catch (Exception e)
  53. {
  54. e.printStackTrace();
  55. }
  56. }
  57. }
  58. }
  59. }

嗯,注释尽可能的详细了,主要就是遍历所有的方法,拿到该方法省的OnClick注解,然后再拿到该注解上的EventBase注解,得到事件监听的需要调用的方法名,类型,和需要调用的方法的名称;通过Proxy和InvocationHandler得到监听器的代理对象,显示设置了方法,最后通过反射设置监听器。

这里有个难点,就是关于DynamicHandler和Proxy的出现,如果不理解没事,后面会详细讲解。

3、DynamicHandler

这里用到了一个类DynamicHandler,就是InvocationHandler的实现类:

[java] view plaincopy
  1. package com.zhy.ioc.view;
  2. import java.lang.ref.WeakReference;
  3. import java.lang.reflect.InvocationHandler;
  4. import java.lang.reflect.Method;
  5. import java.util.HashMap;
  6. public class DynamicHandler implements InvocationHandler
  7. {
  8. private WeakReference<Object> handlerRef;
  9. private final HashMap<String, Method> methodMap = new HashMap<String, Method>(
  10. 1);
  11. public DynamicHandler(Object handler)
  12. {
  13. this.handlerRef = new WeakReference<Object>(handler);
  14. }
  15. public void addMethod(String name, Method method)
  16. {
  17. methodMap.put(name, method);
  18. }
  19. public Object getHandler()
  20. {
  21. return handlerRef.get();
  22. }
  23. public void setHandler(Object handler)
  24. {
  25. this.handlerRef = new WeakReference<Object>(handler);
  26. }
  27. @Override
  28. public Object invoke(Object proxy, Method method, Object[] args)
  29. throws Throwable
  30. {
  31. Object handler = handlerRef.get();
  32. if (handler != null)
  33. {
  34. String methodName = method.getName();
  35. method = methodMap.get(methodName);
  36. if (method != null)
  37. {
  38. return method.invoke(handler, args);
  39. }
  40. }
  41. return null;
  42. }
  43. }

好了,代码就这么多,这样我们就实现了,我们事件的注入~~

效果图:

效果图其实没撒好贴的,都一样~~~

3、关于代理

那么,本文结束了么,没有~~~关于以下几行代码,相信大家肯定有困惑,这几行干了什么?

[java] view plaincopy
  1. //通过InvocationHandler设置代理
  2. DynamicHandler handler = new DynamicHandler(activity);
  3. handler.addMethod(methodName, method);
  4. Object listener = Proxy.newProxyInstance(
  5. listenerType.getClassLoader(),
  6. new Class<?>[] { listenerType }, handler);

InvocationHandler和Proxy成对出现,相信大家如果对Java比较熟悉,肯定会想到Java的动态代理~~~

关于InvocationHandler和Proxy的文章,大家可以参考:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ ps:IBM的技术文章还是相当不错的,毕竟有人审核还有奖金~

但是我们的实现有一定的区别,我为什么说大家疑惑呢,比如反射实现:

mBtn2.setOnClickListener(this);这样的代码,难点在哪呢?

1、mBtn2的获取?so easy

2、调用setOnClickListener ? so easy

but , 这个 this,这个this是OnClickListener的实现类的实例,OnClickListener是个接口~~你的实现类怎么整,听说过反射newInstance对象的,但是你现在是接口!

是吧~现在应该明白上述几行代码做了什么了?实现了接口的一个代理对象,然后在代理类的invoke中,对接口的调用方法进行处理。

4、代码是最好的老师

光说谁都理解不了,你在这xx什么呢??下面看代码,我们模拟实现这样一个情景:

Main类中实现一个Button,Button有两个方法,一个setOnClickListener和onClick,当调用Button的onClick时,触发的事件是Main类中的click方法

涉及到4个类:

Button

[java] view plaincopy
  1. package com.zhy.invocationhandler;
  2. public class Button
  3. {
  4. private OnClickListener listener;
  5. public void setOnClickLisntener(OnClickListener listener)
  6. {
  7. this.listener = listener;
  8. }
  9. public void click()
  10. {
  11. if (listener != null)
  12. {
  13. listener.onClick();
  14. }
  15. }
  16. }

OnClickListener接口

[java] view plaincopy
  1. package com.zhy.invocationhandler;
  2. public interface OnClickListener
  3. {
  4. void onClick();
  5. }

OnClickListenerHandler , InvocationHandler的实现类

[java] view plaincopy
  1. package com.zhy.invocationhandler;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. public class OnClickListenerHandler implements InvocationHandler
  7. {
  8. private Object targetObject;
  9. public OnClickListenerHandler(Object object)
  10. {
  11. this.targetObject = object;
  12. }
  13. private Map<String, Method> methods = new HashMap<String, Method>();
  14. public void addMethod(String methodName, Method method)
  15. {
  16. methods.put(methodName, method);
  17. }
  18. @Override
  19. public Object invoke(Object proxy, Method method, Object[] args)
  20. throws Throwable
  21. {
  22. String methodName = method.getName();
  23. Method realMethod = methods.get(methodName);
  24. return realMethod.invoke(targetObject, args);
  25. }
  26. }

我们的Main

[java] view plaincopy
  1. package com.zhy.invocationhandler;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. public class Main
  6. {
  7. private Button button = new Button();
  8. public Main() throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
  9. {
  10. init();
  11. }
  12. public void click()
  13. {
  14. System.out.println("Button clicked!");
  15. }
  16. public void init() throws SecurityException,
  17. NoSuchMethodException, IllegalArgumentException,
  18. IllegalAccessException, InvocationTargetException
  19. {
  20. OnClickListenerHandler h = new OnClickListenerHandler(this);
  21. Method method = Main.class.getMethod("click", null);
  22. h.addMethod("onClick", method);
  23. Object clickProxy = Proxy.newProxyInstance(
  24. OnClickListener.class.getClassLoader(),
  25. new Class<?>[] { OnClickListener.class }, h);
  26. Method clickMethod = button.getClass().getMethod("setOnClickLisntener",
  27. OnClickListener.class);
  28. clickMethod.invoke(button, clickProxy);
  29. }
  30. public static void main(String[] args) throws SecurityException,
  31. IllegalArgumentException, NoSuchMethodException,
  32. IllegalAccessException, InvocationTargetException
  33. {
  34. Main main = new Main();
  35. main.button.click();
  36. }
  37. }

我们模拟按钮点击:调用main.button.click(),实际执行的却是Main的click方法。

看init中,我们首先初始化了一个OnClickListenerHandler,把Main的当前实例传入,然后拿到Main的click方法,添加到OnClickListenerHandler中的Map中。

然后通过Proxy.newProxyInstance拿到OnClickListener这个接口的一个代理,这样执行这个接口的所有的方法,都会去调用OnClickListenerHandler的invoke方法。

但是呢?OnClickListener毕竟是个接口,也没有方法体~~那咋办呢?这时候就到我们OnClickListenerHandler中的Map中大展伸手了:

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{

String methodName = method.getName();
Method realMethod = methods.get(methodName);
return realMethod.invoke(targetObject, args);
}

我们显示的把要执行的方法,通过键值对存到Map里面了,等调用到invoke的时候,其实是通过传入的方法名,得到Map中存储的方法,然后调用我们预设的方法~。

这样,大家应该明白了,其实就是通过Proxy得到接口的一个代理,然后在InvocationHandler中使用一个Map预先设置方法,从而实现Button的onClick,和Main的click关联上。

现在看我们InjectEvents中的代码:

[java] view plaincopy
  1. //通过InvocationHandler设置代理
  2. DynamicHandler handler = new DynamicHandler(activity);
  3. //往map添加方法
  4. handler.addMethod(methodName, method);
  5. Object listener = Proxy.newProxyInstance(
  6. listenerType.getClassLoader(),
  7. new Class<?>[] { listenerType }, handler);

是不是和我们init中的类似~~

好了,关于如何把接口的回调和我们Activity里面的方法关联上我们也解释完了~~~

注:部分代码参考了xUtils这个框架,毕竟想很完善的实现一个完整的注入不是一两篇博客就可以搞定,但是核心和骨架已经实现了~~大家有兴趣的可以继续去完善~

源码点击下载

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)相关推荐

  1. Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:[张鸿洋的博客] 1.概述 首先我们来吹吹牛,什么叫Io ...

  2. Android:手把手教你打造可缩放移动的ImageView(下)

    在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...

  3. 根据用户查进程_【磨叽教程】Android进阶教程之在Android系统下各进程之间的优先级关系...

    导读:本文大约2000字,预计阅读时间3分钟.本文纯属技术文,无推广. 正文     首先应用进程的生命周期并不由应用本身直接控制,而是由系统综合多种因素来确定的.Android系统有自己的一套标准, ...

  4. Android进阶之路 - 软键盘中右下角的设置与监听

    在项目中,多多少少会遇到修改软键盘右下角按钮的需求,虽然已经写过几次,但是还是觉得在这里专心做个笔记比较放心 ~ 我的那些软键盘Blog ~ Android进阶之路 - 常见软键盘操作行为 Andro ...

  5. 【我的Android进阶之旅】Android 混淆文件资源分类整理之二:将混淆文件拆分成更小粒度的混淆文件

    在我2017年的文章[我的Android进阶之旅]Android 混淆文件资源分类整理中,我已经提及过. 之前将所有的混淆都配置在一个 proguard-rules.pro 这个Android Stu ...

  6. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(一)

    文章大纲 引言 一.Android Storage Access Framework 二.Storage Access Framework 的主要角色成员 1.Document Provider 文件 ...

  7. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(二)

    文章大纲 引言 一.DirectFragment 1.当选中DirectoryFragment中RecyclerView的Item时 2.选中DirectoryFragment中RecyclerVie ...

  8. 【我的Android进阶之旅】Android混淆踩坑之各模块各自单独配置混淆,但是将minifyEnabled设置为true导致的编译错误

    一.背景描述 在之前的两篇文章中 [我的Android进阶之旅]Android 混淆文件资源分类整理 [我的Android进阶之旅]Android 混淆文件资源分类整理之二:将混淆文件拆分成更小粒度的 ...

  9. 【我的Android进阶之旅】Android使用JNI的时候报native crash: A/libc: Fatal signal 4 (SIGILL), code 2 (ILL_ILLOPN)

    一.问题描述 最近在JNI开发中,[我的Android进阶之旅]Android 如何防止 so库文件被未知应用盗用? 抛了一个异常,然后运行的时候报如下所示的错误: 2021-01-08 14:25: ...

最新文章

  1. Java就业前景怎么样?值得学吗?
  2. 导轨式网管型工业交换机优势具体在哪里呢?
  3. SQL ORDER BY 子句
  4. pyqt5设置按钮,移上去变为手型
  5. easy connect无法连接到服务端_故障分析 | 一次因为超过最大连接数的登陆限制
  6. 二、Python开发语音识别
  7. QQ是怎样实现好友桌面快捷方式的?
  8. VMware Workstation Pro 安装教程
  9. 明日之后登不上去一直连接服务器,《明日之后》登不上去怎么办 明日之后进不去怎么回事...
  10. IntelliJ IDEA 好用插件之Maven Helper
  11. html br 缩写,br开头的单词有哪些?-英语单词br-英语-罗酥遮同学
  12. 华为服务器插键盘的位置,云服务器怎么连接键盘
  13. 雷军推红米Redmi独立品牌喊话友商:生死看淡 不服就干
  14. AWS之Glue使用方法
  15. 五、APP分类和手机选择
  16. android点赞取消赞功能吗,Android 仿微博的点赞功能的实现原理(持续点赞再取消)...
  17. 隧道二衬钢筋(一键生成)
  18. 升级autoconf版本 Autoconf version 2.68 or higher is required
  19. ubuntu16.04开机自动挂载nas盘
  20. 搭积木——pythonOJ笔记

热门文章

  1. 怎么写字_写字楼外卖员不让进怎么办?写字楼外卖柜提供解决方案!
  2. java适配器模式火鸡变凤凰是,读《HeadFirst设计模式》笔记之适配器模式
  3. 2019手卫生定义_2021年卫生资格考试部分科目大纲和教材变化归总!
  4. ubuntu linux root,Ubuntu 中的 root 用户:你应该知道的重要事情 | Linux 中国
  5. 《DSP using MATLAB》Problem 6.20
  6. 将权限组件应用到新项目
  7. 数组指针和指针数组的区别(1)
  8. Javascript实现的2048
  9. sql中在查询语句中加判断,控制输出的内容
  10. C语言的条件编译#if, #elif, #else, #endif、#ifdef, #ifndef