国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android應用開發中控制反轉IoC設計模式使用教程

2019-12-12 06:30:00
字體:
來源:轉載
供稿:網友

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);     } 

效果圖:

2016426142543030.gif (382×658)

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;  } } 

好了,代碼就這么多,這樣我們就實現了,我們事件的注入~~
效果圖:

2016426142746902.gif (382×658)

效果圖其實沒撒好貼的,都一樣~~~
(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這個框架,畢竟想很完善的實現一個完整的注入不是一兩篇博客就可以搞定,但是核心和骨架已經實現了~~大家有興趣的可以繼續去完善~

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 奇台县| 炎陵县| 丹阳市| 琼海市| 岳普湖县| 中江县| 北碚区| 交口县| 垣曲县| 会昌县| 泾阳县| 徐州市| 团风县| 韶山市| 淮安市| 南京市| 垣曲县| 库尔勒市| 梁平县| 泰安市| 海城市| 叶城县| 潍坊市| 通道| 什邡市| 新源县| 封丘县| 阳江市| 江华| 寿宁县| 都匀市| 富平县| 渭南市| 潮州市| 洪泽县| 西畴县| 南平市| 天全县| 锦州市| 长治县| 沈阳市|