代理作用:
能增加程序灵活度,完美解决解耦问题。
动态代理可以将调用层和实现层分离,如:retrofit。
动态代理不需要接口的实现类,如:适用于IPC通信进程。
动态代理在静态代理的基础上生成了一个class文件,让它去帮我做事。
静态代理:
举例:我想去买个东西,但是我自己不想去,于是让别人跑腿给我去买,这个就属于代理。
案例中实现如下:
首先定义一个买东西的动作接口:
//定义一个买东西的接口
public interface BuySome {
void thing();
}
我的目的是要去买东西,所以我自己也要实现这个接口,表明自己的目的:
//定义一个我的类,实现我想去做的事情
public class Me implements BuySome{
@Override
public void thing() {
System.out.println("我是被代理类,让代理类帮我去实现这个打印");
}
}
现在我需要张三去帮我做这个动作,那就创建一个人——张三,让他去真正的操作:
public class Zhangsan implements BuySome{
//初始化被代理的类
BuySome ioc =new Me();
@Override
public void thing() {
System.out.println("我是代理类,现在我帮Me去实现");
ioc.thing();
System.out.println("我是代理类,我帮Me去实现了");
}
//测试一下
public static void main(String[] args) {
Zhangsan zhangsan =new Zhangsan();
zhangsan.thing();
}
最后打印结果测试:
----:我是代理类,现在我帮Me去实现
----:我是被代理类,让代理类帮我去实现这个打印
----:我是代理类,我已经帮Me实现了
以上是静态代理操作案例。
接下来我们看动态代理是如何实现:
此案例是演示实现点击和长按代理的操作,使用关键步骤Proxy.newProxyInstance。
准备工作:
创建一个点击的注解Click
/**
* Created by:zx
* on 9/8/21
* 点击的注解
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//android运行在模拟器需要运行时
@EventBase(listenerSetter= "setOnClickListener",listenerType = View.OnClickListener.class,callbackMethod = "onClick")
public @interface Click {
int[] value() default -1;//点击事件不止一个按钮,所以这里定义数组
}
接下来定义一个长按事件的注解:
/**
* Created by:zx
* on 9/8/21
* 长按事件
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//android运行在模拟器需要运行时
@EventBase(listenerSetter= "setOnLongClickListener",listenerType = View.OnLongClickListener.class,callbackMethod = "onLongClick")
public @interface OnLongClick {
int[] value() default -1;
}
再定义一个可以在以上两个点击注解上使用的注解,注意Target的值,并且在里面定义的三个方法分别对应了原本onClickListener/onLongClickListener事件的三个关键:
/**
* Created by:zx
* on 9/8/21
* 使用在Click注解之上,为了方便view不用多次调用onClick、onLongClick等这些类似的方法回调
**/
@Target(ElementType.ANNOTATION_TYPE)//使用在注解上
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//事件三要素
//事件监听的方法setOnClickListener
String listenerSetter();
//事件监听的类型 OnClickListener事件类型
Class<?> listenerType();
//事件回调方法
String callbackMethod();
}
注:
- listenerSetter 对应的是 setOnLongClickListener
- listenerType 对应的是 View.OnLongClickListener的接口
- callbackMethod 对应的是 onLongClick回调方法
如下代码:setOnLongClickListener和setOnClickListener类似流程,所以可以定义同一个注解表示他们。
textView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
定义一个代理类,实现InvocationHandler,接下来需要用到这个类
/**
* Created by:zx
* on 9/8/21
* 动态代理
**/
public class ClickInterface implements InvocationHandler {
Object act;
Method method;
public ClickInterface(Object object, Method method) {
this.act = object;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method me, Object[] args) throws Throwable {
Log.d("zx","代理前---------------");
Object o = method.invoke(act,args);//这里参数名不要搞成invoke方法的名字了,否则会报错
Log.d("zx","代理后---------------");
return o;
}
}
/**
* Created by:zx
* on 9/8/21
* 注解由于是架构,所以里面不能有确切的类型定义 比如Click它是点击事件的具体注解类,后面再加上长按事件就不可以使用了,所以要动态定义以便于长按也可以套用
* 注解+动态代理
**/
public class BindView {
public static void bind(final Object o) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//获取当前Activity
Class<?> cls = o.getClass();
//获取此class中所有的方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
//获取方法上的注解 使用数组是因为一个方法上可能定义的不止一个注解
Annotation[] annotations = method.getAnnotations();
//循环这个注解数组
for (Annotation annotation : annotations) {
//获得注解的类型
Class<?> annType = annotation.annotationType();
//从注解里面的类型中找到EventBase类型的注解(因为他是注解上的注解)
EventBase eventBase = annType.getAnnotation(EventBase.class);
//判断这个注解是否为空
if (eventBase == null) {
continue;
}
//如果有值说明method是需要事件注入的
//获取事件三要素:
//获取第一个要素
String listenerSetter = eventBase.listenerSetter();
//获取事件类型
Class<?> listenerType = eventBase.listenerType();
//获取回调方法 这个参数不要也行 没用到
String callbackMethod = eventBase.callbackMethod();
//获取点击value 也就是注解上的值 也就是R.id...
//注意📢:这个value的值就是你自己注解中定义的那个value 可以不是固定的
Method idValue = annType.getDeclaredMethod("value");
//这里的invoke反射 把注解的ids都反射出来 我的理解 谁里面有你需要的东西就反射(invoke)谁
int[] ids = (int[]) idValue.invoke(annotation);
//接下来ids都拿到了,就和之前的代码逻辑一样了 通过ids得到view 在使用view做事件处理
//findViewbyID找到View
for (int id : ids) {
Method findViewById = cls.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(o, id);//通过反射拿到了view
//拿到View接下来做我们点击事件
if (view == null) {
continue;
}
//使用动态代理 proxy返回类型取决于第二个参数 new Class[]{View.OnClickListener.class
// proxy属性是一个OnClickListener的子类,在内存里生成了一个子类(OnClickListener的实现,帮它做操作)
//原本View.OnClickListener.class 改成 listenerType
Object proxy = Proxy.newProxyInstance(o.getClass().getClassLoader(),
new Class[]{listenerType},
new ClickInterface(o, method));
//原本拿到View要做点击事件,但是这里就不用写具体的点击事件了,直接改成下面两行
// view.setOnClickListener((View.OnClickListener) proxy);
Method method1 = view.getClass().getMethod(listenerSetter, listenerType);
method1.invoke(view, proxy);
}
}
}
}
}
以上就是定义的主要的动态代理的实现类,接下来看一下,如果使用:
/**
* 利用反射或者动态代理,直接调用Click注解就可以拿到id实现view的点击
*/
public class IOCActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ioc);
try {
//这里要绑定一下,也就是把Activity传到BingView的类里去做解析
BindView.bind(this);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//一个方法上可能有多个注解,不要忘记定义clickView方法的参数要和真正的点击事件的回调方法一样传一个View v。
@OnLongClick(R.id.text)
@Click(R.id.text)
public void clickView(View v) {
Toast.makeText(this, "点击了", Toast.LENGTH_LONG).show();
Log.d("zx", "我是在代理中 也就是ClickInterface中invoke方法中method的实现");
}
@OnLongClick(R.id.text)
public boolean longClick(View view) {
Toast.makeText(this, "长按", Toast.LENGTH_LONG).show();
return true;
}
}
以上就是动态代理的简单的实际应用案例,可以帮助更好的理解动态代理。
网友评论