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

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

1、目标效果

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

[java] view plain copy
  1. packagecom.zhy.zhy_xutils_test;
  2. importandroid.app.Activity;
  3. importandroid.os.Bundle;
  4. importandroid.view.View;
  5. importandroid.view.View.OnClickListener;
  6. importandroid.widget.Button;
  7. importandroid.widget.Toast;
  8. importcom.zhy.ioc.view.ViewInjectUtils;
  9. importcom.zhy.ioc.view.annotation.ContentView;
  10. importcom.zhy.ioc.view.annotation.ViewInject;
  11. @ContentView(value=R.layout.activity_main)
  12. publicclassMainActivityextendsActivityimplementsOnClickListener
  13. {
  14. @ViewInject(R.id.id_btn)
  15. privateButtonmBtn1;
  16. @ViewInject(R.id.id_btn02)
  17. privateButtonmBtn2;
  18. @Override
  19. protectedvoidonCreate(BundlesavedInstanceState)
  20. {
  21. super.onCreate(savedInstanceState);
  22. ViewInjectUtils.inject(this);
  23. mBtn1.setOnClickListener(this);
  24. mBtn2.setOnClickListener(this);
  25. }
  26. @Override
  27. publicvoidonClick(Viewv)
  28. {
  29. switch(v.getId())
  30. {
  31. caseR.id.id_btn:
  32. Toast.makeText(MainActivity.this,"Whydoyouclickme?",
  33. Toast.LENGTH_SHORT).show();
  34. break;
  35. caseR.id.id_btn02:
  36. Toast.makeText(MainActivity.this,"Iamsleeping!!!",
  37. Toast.LENGTH_SHORT).show();
  38. break;
  39. }
  40. }
  41. }

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

[java] view plain copy
  1. packagecom.zhy.zhy_xutils_test;
  2. importandroid.view.View;
  3. importandroid.widget.Button;
  4. importandroid.widget.Toast;
  5. importcom.zhy.ioc.view.annotation.ContentView;
  6. importcom.zhy.ioc.view.annotation.OnClick;
  7. importcom.zhy.ioc.view.annotation.ViewInject;
  8. @ContentView(value=R.layout.activity_main)
  9. publicclassMainActivityextendsBaseActivity
  10. {
  11. @ViewInject(R.id.id_btn)
  12. privateButtonmBtn1;
  13. @ViewInject(R.id.id_btn02)
  14. privateButtonmBtn2;
  15. @OnClick({R.id.id_btn,R.id.id_btn02})
  16. publicvoidclickBtnInvoked(Viewview)
  17. {
  18. switch(view.getId())
  19. {
  20. caseR.id.id_btn:
  21. Toast.makeText(this,"InjectBtn01!",Toast.LENGTH_SHORT).show();
  22. break;
  23. caseR.id.id_btn02:
  24. Toast.makeText(this,"InjectBtn02!",Toast.LENGTH_SHORT).show();
  25. break;
  26. }
  27. }
  28. }

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

2、实现

1、注解文件

[java] view plain copy
  1. packagecom.zhy.ioc.view.annotation;
  2. importjava.lang.annotation.ElementType;
  3. importjava.lang.annotation.Retention;
  4. importjava.lang.annotation.RetentionPolicy;
  5. importjava.lang.annotation.Target;
  6. @Target(ElementType.ANNOTATION_TYPE)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public@interfaceEventBase
  9. {
  10. Class<?>listenerType();
  11. StringlistenerSetter();
  12. StringmethodName();
  13. }

[java] view plain copy
  1. packagecom.zhy.ioc.view.annotation;
  2. importjava.lang.annotation.ElementType;
  3. importjava.lang.annotation.Retention;
  4. importjava.lang.annotation.RetentionPolicy;
  5. importjava.lang.annotation.Target;
  6. importandroid.view.View;
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @EventBase(listenerType=View.OnClickListener.class,listenerSetter="setOnClickListener",methodName="onClick")
  10. public@interfaceOnClick
  11. {
  12. int[]value();
  13. }

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

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

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

[java] view plain copy
  1. @OnClick({R.id.id_btn,R.id.id_btn02})
  2. publicvoidclickBtnInvoked(Viewview)

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

[java] view plain copy
  1. publicstaticvoidinject(Activityactivity)
  2. {
  3. injectContentView(activity);
  4. injectViews(activity);
  5. injectEvents(activity);
  6. }

2、injectEvents

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

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

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

3、DynamicHandler

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

[java] view plain copy
  1. packagecom.zhy.ioc.view;
  2. importjava.lang.ref.WeakReference;
  3. importjava.lang.reflect.InvocationHandler;
  4. importjava.lang.reflect.Method;
  5. importjava.util.HashMap;
  6. publicclassDynamicHandlerimplementsInvocationHandler
  7. {
  8. privateWeakReference<Object>handlerRef;
  9. privatefinalHashMap<String,Method>methodMap=newHashMap<String,Method>(
  10. 1);
  11. publicDynamicHandler(Objecthandler)
  12. {
  13. this.handlerRef=newWeakReference<Object>(handler);
  14. }
  15. publicvoidaddMethod(Stringname,Methodmethod)
  16. {
  17. methodMap.put(name,method);
  18. }
  19. publicObjectgetHandler()
  20. {
  21. returnhandlerRef.get();
  22. }
  23. publicvoidsetHandler(Objecthandler)
  24. {
  25. this.handlerRef=newWeakReference<Object>(handler);
  26. }
  27. @Override
  28. publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
  29. throwsThrowable
  30. {
  31. Objecthandler=handlerRef.get();
  32. if(handler!=null)
  33. {
  34. StringmethodName=method.getName();
  35. method=methodMap.get(methodName);
  36. if(method!=null)
  37. {
  38. returnmethod.invoke(handler,args);
  39. }
  40. }
  41. returnnull;
  42. }
  43. }
好了,代码就这么多,这样我们就实现了,我们事件的注入~~

效果图:


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

3、关于代理

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

[java] view plain copy
  1. //通过InvocationHandler设置代理
  2. DynamicHandlerhandler=newDynamicHandler(activity);
  3. handler.addMethod(methodName,method);
  4. Objectlistener=Proxy.newProxyInstance(
  5. listenerType.getClassLoader(),
  6. newClass<?>[]{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 plain copy
  1. packagecom.zhy.invocationhandler;
  2. publicclassButton
  3. {
  4. privateOnClickListenerlistener;
  5. publicvoidsetOnClickLisntener(OnClickListenerlistener)
  6. {
  7. this.listener=listener;
  8. }
  9. publicvoidclick()
  10. {
  11. if(listener!=null)
  12. {
  13. listener.onClick();
  14. }
  15. }
  16. }

OnClickListener接口

[java] view plain copy
  1. packagecom.zhy.invocationhandler;
  2. publicinterfaceOnClickListener
  3. {
  4. voidonClick();
  5. }

OnClickListenerHandler , InvocationHandler的实现类

[java] view plain copy
  1. packagecom.zhy.invocationhandler;
  2. importjava.lang.reflect.InvocationHandler;
  3. importjava.lang.reflect.Method;
  4. importjava.util.HashMap;
  5. importjava.util.Map;
  6. publicclassOnClickListenerHandlerimplementsInvocationHandler
  7. {
  8. privateObjecttargetObject;
  9. publicOnClickListenerHandler(Objectobject)
  10. {
  11. this.targetObject=object;
  12. }
  13. privateMap<String,Method>methods=newHashMap<String,Method>();
  14. publicvoidaddMethod(StringmethodName,Methodmethod)
  15. {
  16. methods.put(methodName,method);
  17. }
  18. @Override
  19. publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
  20. throwsThrowable
  21. {
  22. StringmethodName=method.getName();
  23. MethodrealMethod=methods.get(methodName);
  24. returnrealMethod.invoke(targetObject,args);
  25. }
  26. }

我们的Main

[java] view plain copy
  1. packagecom.zhy.invocationhandler;
  2. importjava.lang.reflect.InvocationTargetException;
  3. importjava.lang.reflect.Method;
  4. importjava.lang.reflect.Proxy;
  5. publicclassMain
  6. {
  7. privateButtonbutton=newButton();
  8. publicMain()throwsSecurityException,IllegalArgumentException,NoSuchMethodException,IllegalAccessException,InvocationTargetException
  9. {
  10. init();
  11. }
  12. publicvoidclick()
  13. {
  14. System.out.println("Buttonclicked!");
  15. }
  16. publicvoidinit()throwsSecurityException,
  17. NoSuchMethodException,IllegalArgumentException,
  18. IllegalAccessException,InvocationTargetException
  19. {
  20. OnClickListenerHandlerh=newOnClickListenerHandler(this);
  21. Methodmethod=Main.class.getMethod("click",null);
  22. h.addMethod("onClick",method);
  23. ObjectclickProxy=Proxy.newProxyInstance(
  24. OnClickListener.class.getClassLoader(),
  25. newClass<?>[]{OnClickListener.class},h);
  26. MethodclickMethod=button.getClass().getMethod("setOnClickLisntener",
  27. OnClickListener.class);
  28. clickMethod.invoke(button,clickProxy);
  29. }
  30. publicstaticvoidmain(String[]args)throwsSecurityException,
  31. IllegalArgumentException,NoSuchMethodException,
  32. IllegalAccessException,InvocationTargetException
  33. {
  34. Mainmain=newMain();
  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 plain copy
  1. //通过InvocationHandler设置代理
  2. DynamicHandlerhandler=newDynamicHandler(activity);
  3. //往map添加方法
  4. handler.addMethod(methodName,method);
  5. Objectlistener=Proxy.newProxyInstance(
  6. listenerType.getClassLoader(),
  7. newClass<?>[]{listenerType},handler);

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

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


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




源码点击下载


更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android(安卓)Wifi模块分析(三)
  7. Android中dispatchDraw分析
  8. Android四大基本组件介绍与生命周期
  9. Android(安卓)MediaPlayer 常用方法介绍

随机推荐

  1. Android(安卓)ADT 14 插件更新说明
  2. Android ICS系统是支持通过互联网时间同
  3. Android线程与异步消息处理机制
  4. Android使用Bmob移动后端云Restful API需
  5. 谈谈对Android定时任务中AlarmManager的
  6. qq聊天界面七:表情的发送接收(用富文本现实
  7. android系统裁剪方法
  8. 【学习Android遇到的错误】Android 开发
  9. Android(安卓)在代码中书写布局(xml)配置
  10. android studio导入gbk编码的工程文件导