欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android应用开发中控制反转IoC设计模式使用教程

程序员文章站 2024-04-03 12:41:16
1、概述 首先我们来吹吹牛,什么叫ioc,控制反转(inversion of control,英文缩写为ioc),什么意思呢? 就是你一个类里面需要用到很多个成员变量,...

1、概述
首先我们来吹吹牛,什么叫ioc,控制反转(inversion of control,英文缩写为ioc),什么意思呢?
就是你一个类里面需要用到很多个成员变量,传统的写法,你要用这些成员变量,那么你就new 出来用呗~~
ioc的原则是:no,我们不要new,这样耦合度太高;你配置个xml文件,里面标明哪个类,里面用了哪些成员变量,等待加载这个类的时候,我帮你注入(new)进去;
这样做有什么好处呢?
 回答这个问题,刚好可以回答另一个问题,很多人问,项目分层开发是吧,分为控制层、业务层、dao层神马的。然后每一层为撒子要一个包放接口,一个包放实现呢?只要一个实现包不行么~刚好,如果你了解了ioc,你就知道这些个接口的作用了,上面不是说,你不用new,你只要声明了成员变量+写个配置文件,有人帮你new;此时,你在类中,就可以把需要使用到的成员变量都声明成接口,然后你会发现,当实现类发生变化的时候,或者切换实现类,你需要做什么呢?你只要在配置文件里面做个简单的修改。如果你用的就是实实在在的实现类,现在换实现类,你需要找到所有声明这个实现类的地方,手动修改类名;如果你遇到了一个多变的老大,是吧,呵呵~
 当然了,很多会觉得,写个配置文件,卧槽,这多麻烦。于是乎,又出现了另一种方案,得,你闲配置文件麻烦,你用注解吧。你在需要注入的成员变量上面给我加个注解,例如:@inject,这样就行了,你总不能说这么个单词麻烦吧~~
 当然了,有了配置文件和注解,那么怎么注入呢?其实就是把字符串类路径变成类么,当然了,反射上场了;话说,很久很久以前,反射很慢啊,嗯,那是很久很久以前,现在已经不是太慢了,当然了肯定达不到原生的速度~~无反射,没有任何框架。
 如果你觉得注解,反射神马的好高级。我说一句:just do it ,你会发现注解就和你写一个普通javabean差不多;反射呢?api就那么几行,千万不要被震慑住~

2、框架实现
得进入正题了,android ioc框架,其实主要就是帮大家注入所有的控件,布局文件什么的。如果你用过xutils,afinal类的框架,你肯定不陌生~
注入view
假设:我们一个activity,里面10来个view。
传统做法:我们需要先给这个activity设置下布局文件,然后在oncreate里面一个一个的findviewbyid把~
目标的做法:activity类上添加个注解,帮我们自动注入布局文科;声明view的时候,添加一行注解,然后自动帮我们findviewbyid;
于是乎我们的目标类是这样的:
 

@contentview(value = r.layout.activity_main) 
public class mainactivity extends baseactivity 
{ 
 @viewinject(r.id.id_btn) 
 private button mbtn1; 
 @viewinject(r.id.id_btn02) 
 private button mbtn2; 

3、编码
(1)定义注解
首先我们需要两个注解文件:

package com.zhy.ioc.view.annotation; 
 
import java.lang.annotation.elementtype; 
import java.lang.annotation.retention; 
import java.lang.annotation.retentionpolicy; 
import java.lang.annotation.target; 
 
@target(elementtype.type) 
@retention(retentionpolicy.runtime) 
public @interface contentview 
{ 
 int value(); 
} 

contentview用于在类上使用,主要用于标明该activity需要使用的布局文件。

 @contentview(value = r.layout.activity_main) 
public class mainactivity 

 
package com.zhy.ioc.view.annotation; 
 
import java.lang.annotation.elementtype; 
import java.lang.annotation.retention; 
import java.lang.annotation.retentionpolicy; 
import java.lang.annotation.target; 
 
@target(elementtype.field) 
@retention(retentionpolicy.runtime) 
public @interface viewinject 
{ 
 int value(); 
} 

在成员变量上使用,用于指定view的id

@viewinject(r.id.id_btn) 
 private button mbtn1; 

简单说一下注解:定义的关键字@interface ; @target表示该注解可以用于什么地方,可能的类型type(类),field(成员变量),可能的类型:

public enum elementtype { 
 /** 
  * class, interface or enum declaration. 
  */ 
 type, 
 /** 
  * field declaration. 
  */ 
 field, 
 /** 
  * method declaration. 
  */ 
 method, 
 /** 
  * parameter declaration. 
  */ 
 parameter, 
 /** 
  * constructor declaration. 
  */ 
 constructor, 
 /** 
  * local variable declaration. 
  */ 
 local_variable, 
 /** 
  * annotation type declaration. 
  */ 
 annotation_type, 
 /** 
  * package declaration. 
  */ 
 package 
} 

就是这些个枚举。
@retention表示:表示需要在什么级别保存该注解信息;我们这里设置为运行时。
可能的类型: 

public enum retentionpolicy { 
 /** 
  * annotation is only available in the source code. 
  */ 
 source, 
 /** 
  * annotation is available in the source code and in the class file, but not 
  * at runtime. this is the default policy. 
  */ 
 class, 
 /** 
  * annotation is available in the source code, the class file and is 
  * available at runtime. 
  */ 
 runtime 
} 

这些个枚举~

(2)mainactivity

package com.zhy.zhy_xutils_test; 
 
import android.app.activity; 
import android.os.bundle; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.widget.button; 
import android.widget.toast; 
 
import com.zhy.ioc.view.viewinjectutils; 
import com.zhy.ioc.view.annotation.contentview; 
import com.zhy.ioc.view.annotation.viewinject; 
 
@contentview(value = r.layout.activity_main) 
public class mainactivity extends activity implements onclicklistener 
{ 
 @viewinject(r.id.id_btn) 
 private button mbtn1; 
 @viewinject(r.id.id_btn02) 
 private button mbtn2; 
 
 @override 
 protected void oncreate(bundle savedinstancestate) 
 { 
  super.oncreate(savedinstancestate); 
   
  viewinjectutils.inject(this); 
 
  mbtn1.setonclicklistener(this); 
  mbtn2.setonclicklistener(this); 
 } 
 
 @override 
 public void onclick(view v) 
 { 
  switch (v.getid()) 
  { 
  case r.id.id_btn: 
   toast.maketext(mainactivity.this, "why do you click me ?", 
     toast.length_short).show(); 
   break; 
 
  case r.id.id_btn02: 
   toast.maketext(mainactivity.this, "i am sleeping !!!", 
     toast.length_short).show(); 
   break; 
  } 
 } 
 
} 

注解都写好了,核心的代码就是viewinjectutils.inject(this)了~

(3)viewinjectutils
a、首先是注入主布局文件的代码:

/** 
  * 注入主布局文件 
  * 
  * @param activity 
  */ 
 private static void injectcontentview(activity activity) 
 { 
  class<? extends activity> clazz = activity.getclass(); 
  // 查询类上是否存在contentview注解 
  contentview contentview = clazz.getannotation(contentview.class); 
  if (contentview != null)// 存在 
  { 
   int contentviewlayoutid = contentview.value(); 
   try 
   { 
    method method = clazz.getmethod(method_set_contentview, 
      int.class); 
    method.setaccessible(true); 
    method.invoke(activity, contentviewlayoutid); 
   } catch (exception e) 
   { 
    e.printstacktrace(); 
   } 
  } 
 } 

通过传入的activity对象,获得它的class类型,判断是否写了contentview这个注解,如果写了,读取它的value,然后得到setcontentview这个方法,使用invoke进行调用;
有个常量:

private static final string method_set_contentview = "setcontentview"; 

b、接下来是注入views

private static final string method_find_view_by_id = "findviewbyid"; 
 /** 
  * 注入所有的控件 
  * 
  * @param activity 
  */ 
 private static void injectviews(activity activity) 
 { 
  class<? extends activity> clazz = activity.getclass(); 
  field[] fields = clazz.getdeclaredfields(); 
  // 遍历所有成员变量 
  for (field field : fields) 
  { 
    
   viewinject viewinjectannotation = field 
     .getannotation(viewinject.class); 
   if (viewinjectannotation != null) 
   { 
    int viewid = viewinjectannotation.value(); 
    if (viewid != -1) 
    { 
     log.e("tag", viewid+""); 
     // 初始化view 
     try 
     { 
      method method = clazz.getmethod(method_find_view_by_id, 
        int.class); 
      object resview = method.invoke(activity, viewid); 
      field.setaccessible(true); 
      field.set(activity, resview); 
     } catch (exception e) 
     { 
      e.printstacktrace(); 
     } 
 
    } 
   } 
 
  } 
 
 } 

获取声明的所有的属性,遍历,找到存在viewinject注解的属性,或者其value,然后去调用findviewbyid方法,最后把值设置给field~~~
好了,把这两个方法写到inject里面就好了。

 public static void inject(activity activity) 
 { 
   
  injectcontentview(activity); 
  injectviews(activity); 
   
 } 

效果图:

Android应用开发中控制反转IoC设计模式使用教程

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

package com.zhy.zhy_xutils_test; 
 
import android.view.view; 
import android.widget.button; 
import android.widget.toast; 
 
import com.zhy.ioc.view.annotation.contentview; 
import com.zhy.ioc.view.annotation.onclick; 
import com.zhy.ioc.view.annotation.viewinject; 
 
@contentview(value = r.layout.activity_main) 
public class mainactivity extends baseactivity 
{ 
 @viewinject(r.id.id_btn) 
 private button mbtn1; 
 @viewinject(r.id.id_btn02) 
 private button mbtn2; 
 
 @onclick({ r.id.id_btn, r.id.id_btn02 }) 
 public void clickbtninvoked(view view) 
 { 
  switch (view.getid()) 
  { 
  case r.id.id_btn: 
   toast.maketext(this, "inject btn01 !", toast.length_short).show(); 
   break; 
  case r.id.id_btn02: 
   toast.maketext(this, "inject btn02 !", toast.length_short).show(); 
   break; 
  } 
 } 
 
} 

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

(1)注解文件

package com.zhy.ioc.view.annotation; 
 
import java.lang.annotation.elementtype; 
import java.lang.annotation.retention; 
import java.lang.annotation.retentionpolicy; 
import java.lang.annotation.target; 
 
@target(elementtype.annotation_type) 
@retention(retentionpolicy.runtime) 
public @interface eventbase 
{ 
 class<?> listenertype(); 
 
 string listenersetter(); 
 
 string methodname(); 
} 

 
package com.zhy.ioc.view.annotation; 
 
import java.lang.annotation.elementtype; 
import java.lang.annotation.retention; 
import java.lang.annotation.retentionpolicy; 
import java.lang.annotation.target; 
 
import android.view.view; 
 
@target(elementtype.method) 
@retention(retentionpolicy.runtime) 
@eventbase(listenertype = view.onclicklistener.class, listenersetter = "setonclicklistener", methodname = "onclick") 
public @interface onclick 
{ 
 int[] value(); 
} 

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

复制代码 代码如下:

listenertype = view.onclicklistener.class, listenersetter = "setonclicklistener", methodname = "onclick"
onclick是用于写在activity的某个方法上的:
 @onclick({ r.id.id_btn, r.id.id_btn02 }) 
 public void clickbtninvoked(view view) 

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

public static void inject(activity activity) 
 { 
  injectcontentview(activity); 
  injectviews(activity); 
  injectevents(activity); 
 } 

(2)injectevents

/** 
  * 注入所有的事件 
  * 
  * @param activity 
  */ 
 private static void injectevents(activity activity) 
 { 
   
  class<? extends activity> clazz = activity.getclass(); 
  method[] methods = clazz.getmethods(); 
  //遍历所有的方法 
  for (method method : methods) 
  { 
   annotation[] annotations = method.getannotations(); 
   //拿到方法上的所有的注解 
   for (annotation annotation : annotations) 
   { 
    class<? extends annotation> annotationtype = annotation 
      .annotationtype(); 
    //拿到注解上的注解 
    eventbase eventbaseannotation = annotationtype 
      .getannotation(eventbase.class); 
    //如果设置为eventbase 
    if (eventbaseannotation != null) 
    { 
     //取出设置监听器的名称,监听器的类型,调用的方法名 
     string listenersetter = eventbaseannotation 
       .listenersetter(); 
     class<?> listenertype = eventbaseannotation.listenertype(); 
     string methodname = eventbaseannotation.methodname(); 
 
     try 
     { 
      //拿到onclick注解中的value方法 
      method amethod = annotationtype 
        .getdeclaredmethod("value"); 
      //取出所有的viewid 
      int[] viewids = (int[]) amethod 
        .invoke(annotation, null); 
      //通过invocationhandler设置代理 
      dynamichandler handler = new dynamichandler(activity); 
      handler.addmethod(methodname, method); 
      object listener = proxy.newproxyinstance( 
        listenertype.getclassloader(), 
        new class<?>[] { listenertype }, handler); 
      //遍历所有的view,设置事件 
      for (int viewid : viewids) 
      { 
       view view = activity.findviewbyid(viewid); 
       method seteventlistenermethod = view.getclass() 
         .getmethod(listenersetter, listenertype); 
       seteventlistenermethod.invoke(view, listener); 
      } 
 
     } catch (exception e) 
     { 
      e.printstacktrace(); 
     } 
    } 
 
   } 
  } 
 
 } 

嗯,注释尽可能的详细了,主要就是遍历所有的方法,拿到该方法省的onclick注解,然后再拿到该注解上的eventbase注解,得到事件监听的需要调用的方法名,类型,和需要调用的方法的名称;通过proxy和invocationhandler得到监听器的代理对象,显示设置了方法,最后通过反射设置监听器。
这里有个难点,就是关于dynamichandler和proxy的出现,如果不理解没事,后面会详细讲解。

(3)dynamichandler
这里用到了一个类dynamichandler,就是invocationhandler的实现类:

package com.zhy.ioc.view; 
 
import java.lang.ref.weakreference; 
import java.lang.reflect.invocationhandler; 
import java.lang.reflect.method; 
import java.util.hashmap; 
 
public class dynamichandler implements invocationhandler 
{ 
 private weakreference<object> handlerref; 
 private final hashmap<string, method> methodmap = new hashmap<string, method>( 
   1); 
 
 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(); 
 } 
 
 public void sethandler(object handler) 
 { 
  this.handlerref = new weakreference<object>(handler); 
 } 
 
 @override 
 public object invoke(object proxy, method method, object[] args) 
   throws throwable 
 { 
  object handler = handlerref.get(); 
  if (handler != null) 
  { 
   string methodname = method.getname(); 
   method = methodmap.get(methodname); 
   if (method != null) 
   { 
    return method.invoke(handler, args); 
   } 
  } 
  return null; 
 } 
} 

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

Android应用开发中控制反转IoC设计模式使用教程

效果图其实没撒好贴的,都一样~~~
(3)关于代理
那么,本文结束了么,没有~~~关于以下几行代码,相信大家肯定有困惑,这几行干了什么?

//通过invocationhandler设置代理       

dynamichandler handler = new dynamichandler(activity); 
      handler.addmethod(methodname, method); 
      object listener = proxy.newproxyinstance( 
        listenertype.getclassloader(), 
        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);这样的代码,难点在哪呢?
a、mbtn2的获取?so easy
b、调用setonclicklistener ? so easy
but , 这个 this,这个this是onclicklistener的实现类的实例,onclicklistener是个接口~~你的实现类怎么整,听说过反射newinstance对象的,但是你现在是接口!
是吧~现在应该明白上述几行代码做了什么了?实现了接口的一个代理对象,然后在代理类的invoke中,对接口的调用方法进行处理。
(4)代码是最好的老师
光说谁都理解不了,你在这xx什么呢??下面看代码,我们模拟实现这样一个情景:
main类中实现一个button,button有两个方法,一个setonclicklistener和onclick,当调用button的onclick时,触发的事件是main类中的click方法
涉及到4个类:
button

package com.zhy.invocationhandler; 
 
public class button 
{ 
 private onclicklistener listener; 
 
 public void setonclicklisntener(onclicklistener listener) 
 { 
 
  this.listener = listener; 
 } 
 
 public void click() 
 { 
  if (listener != null) 
  { 
   listener.onclick(); 
  } 
 } 
} 

onclicklistener接口

package com.zhy.invocationhandler; 
 
public interface onclicklistener 
{ 
 void onclick(); 
} 

onclicklistenerhandler , invocationhandler的实现类

package com.zhy.invocationhandler; 
 
import java.lang.reflect.invocationhandler; 
import java.lang.reflect.method; 
import java.util.hashmap; 
import java.util.map; 
 
public class onclicklistenerhandler implements invocationhandler 
{ 
 private object targetobject; 
 
 public onclicklistenerhandler(object object) 
 { 
  this.targetobject = object; 
 } 
 
 private map<string, method> methods = new hashmap<string, method>(); 
 
 public void addmethod(string methodname, method method) 
 { 
  methods.put(methodname, method); 
 } 
 
 @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); 
 } 
 
} 

我们的main

package com.zhy.invocationhandler; 
 
import java.lang.reflect.invocationtargetexception; 
import java.lang.reflect.method; 
import java.lang.reflect.proxy; 
 
public class main 
{ 
 private button button = new button(); 
  
 public main() throws securityexception, illegalargumentexception, nosuchmethodexception, illegalaccessexception, invocationtargetexception 
 { 
  init(); 
 } 
 
 public void click() 
 { 
  system.out.println("button clicked!"); 
 } 
 
 public void init() throws securityexception, 
   nosuchmethodexception, illegalargumentexception, 
   illegalaccessexception, invocationtargetexception 
 { 
  onclicklistenerhandler h = new onclicklistenerhandler(this); 
  method method = main.class.getmethod("click", null); 
  h.addmethod("onclick", method); 
  object clickproxy = proxy.newproxyinstance( 
    onclicklistener.class.getclassloader(), 
    new class<?>[] { onclicklistener.class }, h); 
  method clickmethod = button.getclass().getmethod("setonclicklisntener", 
    onclicklistener.class); 
  clickmethod.invoke(button, clickproxy); 
   
 } 
 
 public static void main(string[] args) throws securityexception, 
   illegalargumentexception, nosuchmethodexception, 
   illegalaccessexception, invocationtargetexception 
 { 
 
  main main = new main(); 
   
  main.button.click(); 
 } 
 
} 

我们模拟按钮点击:调用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中的代码:

//通过invocationhandler设置代理 
      dynamichandler handler = new dynamichandler(activity); 
      //往map添加方法 
      handler.addmethod(methodname, method); 
      object listener = proxy.newproxyinstance( 
        listenertype.getclassloader(), 
        new class<?>[] { listenertype }, handler); 

是不是和我们init中的类似~~
好了,关于如何把接口的回调和我们activity里面的方法关联上我们也解释完了~~~

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