Android 設計模式之代理模式

一、代理模式的介紹

定義:為其他的對象提供一種代理,控制這個對象的訪問。
使用場景:當無法或者不想直接訪問某個對象后者訪問某個對象存在困難時可以通過一個代理對象來間接訪問,為了保證客戶端使用的透明性,委托對象與代理對象需要實現相同的接口。
角色劃分:目標接口、目標對象、代理對象
實現方式:靜態代理、動態代理

在我們的平時開發中,所使用的一些開源框架也有應用,如 XUtils 框架、Retrofit 框架等,Android 系統源碼中也有大量應用,如
ActivityManagerProxy 代理類,對于 MVP 架構設計、插件化架構設計,代理模式更顯神通。下面,通過仿照XUtils IOC 實現方式進行對代理模式的深入研究。

二、XUtils 框架分析

XUtils 主要通過注解的方式進行 UI,資源和事件綁定,下面主要對 XUtils
中 ViewUtils 模塊IOC框架的實現進行分析。
IOC實現哪些功能?

  • 第一個功能:布局文件注入
    第一步:新建一個布局注解
    第二步:注入布局

  • 第二個功能:View注入
    第一步:新建一個View注解
    第二步:注入View

  • 第三個功能:事件注入
    第一步:新建一個事件注解
    第二步:注入事件

XUtils動態代理角色:
目標接口:監聽器(例如:OnClickListener、OnLongClickListener等等...)
目標對象:View(Button、TextView等等...)
代理對象:代碼proxy對象(Proxy.newProxyInstance創建返回的對象,就是我們的代理對象)->本質:就是對方法監聽

1.布局文件注入注解

 //類注解
//Target:作用目標->作用在類身上(ElementType.TYPE)
@Target(ElementType.TYPE)
//Retention:生命周期->運行時注解(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    //布局ID
    int value();
}

2.View注入注解

 //Target:作用目標->作用在屬性身上(ElementType.FIELD)
@Target(ElementType.FIELD)
//Retention:生命周期->運行時注解(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    //View的ID
    int value();
}

3.事件注入注解

 //動態指定事件
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
    int[] value();
    Class<?> type() default View.OnClickListener.class;
    String setter() default "";
    String method() default "";
}

3.1注解事件的管理類

public class EventListenerManager {

    private static DynamicHandler dynamicHandler;

    private EventListenerManager() {
    }

    public static void addEventMethod_2_0(
            Annotation eventAnnotation, Object handler, Method method, Object view) {
        try {
            if (view != null) {
                EventBase eventBase = eventAnnotation.annotationType()
                        .getAnnotation(EventBase.class);
                // 監聽類型:OnClickListener、OnTouchListener、OnLongClickListener等等......
                Class<?> listenerType = eventBase.listenerType();
                // 事件源(你要給那個View綁定監聽,而且該監聽對應的方法)
                // View.setOnClickListener() View.setOnTouchListener
                // View.setOnLongClickListener
                String listenerSetter = eventBase.listenerSetter();
                // 監聽方法: onClick方法、onTouch、onLongClick方法
                String methodName = eventBase.methodName();

                // 從緩存中獲取
                // 提高了性能,節約內存

                Object proxy = null;

                // 第一次添加監聽
                dynamicHandler = new DynamicHandler(handler);
                dynamicHandler.addMethod(methodName, method);

                // proxy:代理對象
                proxy = Proxy.newProxyInstance(
                        listenerType.getClassLoader(),
                        new Class<?>[]{listenerType}, dynamicHandler);

                // 綁定監聽
                Method setEventListenerMethod = view.getClass().getMethod(
                        listenerSetter, listenerType);
                setEventListenerMethod.invoke(view, proxy);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public static void addEventMethod_3_0(
            Event event, Object handler, Method method, Object view) {
        try {
            if (view != null && event != null) {
                // 監聽類型:OnClickListener、OnTouchListener、OnLongClickListener等等......
                Class<?> listenerType = event.type();
                // 事件源(你要給那個View綁定監聽,而且該監聽對應的方法)
                // View.setOnClickListener() View.setOnTouchListener
                // View.setOnLongClickListener
                String listenerSetter = event.setter();
                // 監聽方法: onClick方法、onTouch、onLongClick方法
                String methodName = event.method();

                // 從緩存中獲取
                // 提高了性能,節約內存

                Object proxy = null;

                // 第一次添加監聽
                dynamicHandler = new DynamicHandler(handler);
                dynamicHandler.addMethod(methodName, method);

                // proxy:代理對象
                proxy = Proxy.newProxyInstance(
                        listenerType.getClassLoader(),
                        new Class<?>[]{listenerType}, dynamicHandler);

                // 綁定監聽
                Method setEventListenerMethod = view.getClass().getMethod(
                        listenerSetter, listenerType);
                setEventListenerMethod.invoke(view, proxy);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    // WeakReference?為什么?
    // 第一點:及時清理內存
    // 第二點:Activity很有可能會被意外釋放(意外關閉,而這個時候你剛好執行代碼到了控件的加載)
    // 添加軟引用目的:為了防止對象意外被釋放關閉而產生異常(典型:空指針異常)
    public static class DynamicHandler implements InvocationHandler {
        private WeakReference<Object> handlerRef;
        private final HashMap<String, Method> methodMap = new HashMap<String, Method>(
                1);

        // 目標對象: Activity、Fragment
        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;
        }
    }
}

4.最終注解的工具類

public class InjectUtils {

   public static void inject(Object obj) {
       injectLayout(obj);
       Map<Integer, Object> viewMap = injectView(obj);
//        injectEvent_2_0(obj, viewMap);
       injectEvent_3_0(obj, viewMap);
   }


   public static void injectLayout(Object obj) {
       // 獲取Activity的ContentView的注解
       Class<?> handlerType = obj.getClass();
       try {
           //獲取類對象身上的注解
           ContentView contentView = handlerType.getAnnotation(ContentView.class);
           if (contentView != null) {
               //獲取布局ID
               int viewId = contentView.value();
               if (viewId > 0) {
                   Method setContentViewMethod = handlerType.getMethod(
                           "setContentView", int.class);
                   setContentViewMethod.invoke(obj, viewId);
               }
           }
       } catch (Throwable ex) {
           ex.printStackTrace();
       }
   }

   public static Map<Integer, Object> injectView(Object handler) {
       Map<Integer, Object> viewMap = new HashMap<Integer, Object>();
       //獲取類對象
       Class<?> handlerType = handler.getClass();
       //獲取對象屬性列表
       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()) ||
               /* 不注入基本類型字段(int、double、float、char、boolean等等...) */fieldType.isPrimitive() ||
               /* 不注入數組類型字段 */fieldType.isArray()) {
                   continue;
               }

               //獲取屬性注解
               ViewInject viewInject = field.getAnnotation(ViewInject.class);
               if (viewInject != null) {
                   try {
                       //獲取ViewID
                       int viewId = viewInject.value();
                       //獲取findViewById方法對象
                       Method findViewByIdMethod = handlerType.getMethod(
                               "findViewById", int.class);
                       //執行findViewById方法,獲取對象
                       Object view = findViewByIdMethod.invoke(handler,viewId);
                       if (view != null) {
                           //修改訪問權限(private)
                           //setAccessible:將屬性修飾符修改為public
                           field.setAccessible(true);
                           //賦值
                           field.set(handler, view);

                           viewMap.put(viewId, view);
                       } else {
                           throw new RuntimeException(
                                   "Invalid @ViewInject for "
                                           + handlerType.getSimpleName() + "."
                                           + field.getName());
                       }
                   } catch (Throwable ex) {
                       ex.printStackTrace();
                   }
               }
           }
       }
       return viewMap;
   }


   //2.0版本->實現
   public static void injectEvent_2_0(Object obj, Map<Integer, Object> viewMap){
       //獲取類對象
       Class<?> handlerType = obj.getClass();
       //獲取對象方法->activity
       Method[] methods = handlerType.getDeclaredMethods();
       if (methods != null && methods.length > 0) {
           //遍歷方法
           for (Method method : methods) {
               //獲取方法注解
               Annotation[] annotations = method.getDeclaredAnnotations();
               if (annotations != null && annotations.length > 0) {
                   //遍歷注解目的:為了獲取我們想要的注解對象
                   for (Annotation annotation : annotations) {
                       //獲取注解身上注解
                       //獲取注解類型
                       Class<?> annType = annotation.annotationType();
                       if (annType.getAnnotation(EventBase.class) != null) {
                           method.setAccessible(true);
                           try {
                               //獲取注解value方法
                               Method valueMethod = annType.getDeclaredMethod("value");
                               //獲取OnClick、OnLongClick等等....注解身上的value方法
                               //values說白了就是控件的id數組
                               int[] values = (int[]) valueMethod.invoke(annotation);

                               //遍歷id數組
                               for (int i = 0; i < values.length; i++) {
                                   int viewId = values[i];
                                   Object view = viewMap.get(viewId);
                                   //對事件進行動態代理
                                   EventListenerManager.addEventMethod_2_0(annotation, obj, method, view);
                               }
                           } catch (Throwable e) {
                               e.printStackTrace();
                           }
                       }
                   }
               }
           }
       }
   }

   //3.0版本->實現
   public static void injectEvent_3_0(Object handler, Map<Integer, Object> viewMap){
       //獲取類對象
       Class<?> handlerType = handler.getClass();
       //獲取對象方法->activity
       Method[] methods = handlerType.getDeclaredMethods();
       if (methods != null && methods.length > 0) {
           for (Method method : methods) {

               // 注意:靜態方法不允許添加控件注解,私有方法運行訪問,非私有方法不允許訪問
               // 在XUtils框架3.0之后,要求我們的方法必須是私有方法(注意:public不行)
               // 希望該方法配置了注解,不希望子類繼承,只有當前類可以享受
               if (Modifier.isStatic(method.getModifiers())) {
                   continue;
               }

               // 檢查當前方法是否是event注解的方法
               Event event = method.getAnnotation(Event.class);
               if (event != null) {
                   try {
                       // id參數
                       int[] values = event.value();
                       // 循環所有id,生成ViewInfo并添加代理反射
                       for (int i = 0; i < values.length; i++) {
                           int valueId = values[i];
                           if (valueId > 0) {
                               Object view = viewMap.get(valueId);
                               // ViewInfo info = new ViewInfo();
                               // 不管你再怎么樣,永遠都會創建對象
                               method.setAccessible(true);
                               EventListenerManager.addEventMethod_3_0(event, handler, method, view);
                           }
                       }
                   } catch (Throwable ex) {
                       ex.printStackTrace();
                   }
               }
           }
       }
   }

}

三、使用

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @ViewInject(R.id.bt_1)
    private Button bt_1;
    @ViewInject(R.id.bt_2)
    private Button bt_2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
    }

    //2.0版本->寫法
//    @OnClick({R.id.tv_text, R.id.tv_text_a})
//    public void click(View v){
//        Toast.makeText(this,"點擊了TextView", Toast.LENGTH_LONG).show();
//    }

    //3.0版本->寫法
    @Event(
            value = {R.id.bt_1, R.id.bt_2},
            type = View.OnClickListener.class,
            setter = "setOnClickListener",
            method = "onClick")
    public void click(View v) {
        if (v.getId() == R.id.bt_1) {
            Toast.makeText(this, "點擊了Button->1", Toast.LENGTH_LONG).show();
        } else if (v.getId() == R.id.bt_2) {
            Toast.makeText(this, "點擊了Button->2", Toast.LENGTH_LONG).show();
        }
    }
}

四、總結

對于代理模式,我認為需要不斷的實踐,代碼是最好的老師,多去運用在自己的項目當中去,最后,附上我以前寫的一個IOC注解框架,也是使用注解反射結合XUtils和ButterKnife實現方式,可拓展的開源框架,感興趣的可以去瞧瞧,地址:Vegen的Github:自己打造IOC注解框架 ,覺得不錯給個star鼓勵下嘻嘻。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容