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阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容