RxBus封装(IOC+动态代理设计模式) — 像EventBus3,otto一样优雅
本文将通过封装RxJava实现像EventBus3或者otto注解的方式进行消息传递 :
最近在使用RxBus的时候总有这么一种感觉,如果使用一般方式进行消息传递效果是这样的 ,关于一般的RxBus怎么实现,这里就不多做赘述,毕竟度娘上有很多大牛都有封装教程:
RxBus.getInstance()
.register(this)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
@Override
public void call(Object o) {
//do something
}
});
如果使用这种方式,并且这个页面中需要注册多个订阅者,必定会造成页面代码比较乱,而且使用以上方法也没有一个明确的标识.
我就在想可不可以通过otto,或者EventBus3注解的方法进行订阅,这俩框架虽好,但是EventBus3有50多kb,而且我们已经集成了rxJava,在这种情况下根本就没有必要引入可以实现同样功能的框架,所以最好的方式就是在原有RxJava的基础上进行二次封装,已达到我们的最终目的.
期望最终实现的效果,其实这就是通过 IOC(控制反转) 的方式进行封装
我们以post为例,被观察者发送一个信息, 比如在下载服务中发送更新进度条的信息:
@Override
public void updateProgress(String path, double per) {
//per为实际的进度, updateProgress 是为这个事件添加一个标记,用来区分事件
RxBus.getInstance().post("updateProgress", per);
}
activity中根据接收的信息来更新UI
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo);
//step 1 : 注册订阅者
RxBus.getInstance().register(this);
}
//step 2 : 自定义方法名,根据自定义注解中的标记接收消息,并做合适的逻辑
// 这个注解是用来标记哪个方法将要注册成订阅者
@Subscriber(subscriberTo = "updateProgress")
public void onProgress(double o){
//do something
Log.e(TAG, "onProgress: "+o );
}
//step 3 : 回收资源
RxBus.getInstance().unregister(this);`
在封装之前我们先来聊聊什么是IOC,什么是动态代理:
- 关于ioc
IOC(Inversion of Control),直观地讲,就是容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在。控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。IoC还有另外一个名字——“依赖注入(Dependency Injection)”。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象地说,即由容器动态地将某种依赖关系注入到组件之中。而安卓中的ioc往往离不开一个设计模式,动态代理,这个我们后面再说.
IOC一个常用的例子就是使用黄油刀的时候在方法或者类上使用注解,将一些常用的ui逻辑分离开,让我们不必要的去关注这些逻辑,
另外一个例子就是Retrofit的使用时会在接口中定义很多注解,这些注解提前定义了方法或者参数的作用,这样就可以让retrofit内部通过解析注解来执行网络请求,使的我们的http层解耦更加彻底.
他俩共同的特点就是使用了注解提前标注了方法的使用作用,并通过动态代理来实现最终的逻辑
- 关于动态代理设计模式
了解动态代理之前先来看看静态代理模式,静态代理 : 代理设计在Java开发中使用较多的一种设计模式,所谓的代理设计就是指由一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理:
//抽象类
public abstract class Subject {
abstract void visit();
}
//真实主题类
public class RealSubject extends Subject {
@Overrider
public void visit(){
//do someting
}
}
//创建代理主题类
public class ProxySubject extends Subject {
public ClickSubject mSubject;
public ProxySubject(View.OnClickListener clickSubject){
this.mSubject = clickSubject;
}
@Overrider
public void visit(){
// 可以在真实主题类调用之前做其他操作
mSubject.visit();
// 可以在真实主题类调用之后做其他操作
}
}
//使用
public static void main(String[] args){
ProxySubject subject = new ProxySubject(new RealSubject);
//调用代理类方法,其实最终还是会调用真实主题类中的方法,但是调用之前会调用代理主题中的方法做一些通用操作
subject.visit();
}
但是静态代理有弊端,(1)一个代理类只能为一个接口服务,而且代理类要知道代理谁 (2)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。(3)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
那么如果我们压根就不知道我们要代理谁的情况下,并且要避免以上情况怎么办?这就需要用到动态代理,动态代理是反射机制动态的生成代理对象,也就说我们根本不用关心我们要代理谁,代理谁会在执行阶段决定.而在java中我们使用java提供给我们的动态代理的一个接口InvocationHandler实现:
public abstract class Subject {
abstract void visit();
}
//真实主题类
public class RealSubject extends Subject {
@Overrider
public void visit(){
//do someting
}
}
public class ProxyHandler implements InvocationHandler {
private Object mSubject;
public ProxyHandler(Object subject) {
this.mSubject = subject;
}
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {
// 可以在真实主题类调用之前做其他操作
/**转调具体目标对象的方法.
最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处(InvocationHandler.invoke)。
这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。**/
return method.invoke( proxied, args);
// 可以在真实主题类调用之后做其他操作
}
}
public static void main(String args[]) {
//只要传入需要被代理的对象,即使要
Subject proxySubject = (Subject)Proxy.newProxyInstance(new RealSubject().class.getClassLoader(), new Class[]{Subject.class}, new ProxyHandler(real));
proxySubject.visit();
}
对于想要实现EventBus3和otto相同的效果,必须要了解的两个在RxJava中有相似性质的两个对象
- PublishSubject
与普通的Subject不同,在订阅时并不立即触发订阅事件,而是允许我们在任意时刻手动调用onNext(),onError(),onCompleted来触发事件.
可以看到PublishSubject与普通的Subject最大的不同就是其可以先订阅事件,然后在某一时刻手动调用方法来触发事件
,当一个界面发生改变,通知另一个界面做出响应,像这种场景就很适合使用
也就是说只有在调用了onNext()之后才会触发事件,将消息发送过来,这一点和EventBus3的post事件很相似.
- ReplaySubject
ReplaySubject 可以缓存所有发射给他的数据。当一个新的订阅者订阅的时候,缓存的所有数据都会发射给这个订阅者。也就是说如果一个数据在发射的时候,订阅者并没有创建,那么他就会缓存起来,这个和EventBus的postSticky() 发送粘性事件很相似.
运用这两个类就可以实现EventBus的post 和 postSticky 相同的功能,这也就是RxBus的原理.
下面开始进行封装逻辑
总结一下为什么要用使用动态代理 + IOC思想 :
再次看一下我们要实现的效果 :
@Override
public void updateProgress(String path, double per) {
//per为实际的进度, updateProgress 是为这个事件添加一个标记,用来区分事件
RxBus.getInstance().post("updateProgress", per);
}
1.首先我们并不知道调用者要定义什么样的方法,在什么样的方法中接收传递过来的参数,我们只知道我们要执行
subscriber(new action1(){ public void onCall{}}); 在call方法中拿到参数并返回给用户自定义的方法,
RxBus.getInctance().register().subscriber(new Action1(){
@Overrid
public void call(per){
//此处执行用户自定义的方法;
updateProgress(per);
}
})
这样看起来就像是动态代理设计模式,我们并不知道传递进来的是什么主题类(用户自定义的接收方法),只要在主题类传进来之前做一些通用逻辑,比如拿到RxBus需要传递的信息,然后通过传递给主题类,传递出去即可.
1.定义自定义注解,我们要定义一个Subscriber的注解,用来标记需要订阅的方法,我们都知道事件总线是通过类似观察者模式进行处理的,所以一定会有通过回调函数将值返回,而回调函数分为三大部分:(1)监听方法,(2)监听类型,(3)回调方法,所以我们的思路是通过反射调用回调三元素实现去调用回调方法,最后再把结果返回到我们自定义的方法上,关于如何把结果返回到我们自定的方法上,最后再实现,先看看如何定义回调三要素:
一般的RxBus回调过程如下
RxBus.getInstance()
.register(this)
.observeOn(AndroidSchedulers.mainThread())
//subscribe 为监听方法 Action1 为监听类型 call为回调方法
.subscribe(new Action1<Object>() {
@Override
public void call(Object o) {
//do something
}
});
2.我们将回调三元素封装到我们的自定义的注解中,当然还有将标记封装进来
//定义一个事件基类
public @interface EventBase {
/**
* 要执行一个事件监听的三要素
* 1.监听方法
* 2.监听类型
* 3.回调方法
*/
String listenerSetter();
Class<?> listenerType();
String callBackMethod();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义回调三要素
@EventBase(listenerSetter = "subscribe",listenerType = Action1.class,callBackMethod = "call")
public @interface Subscriber {
//定义标记
String subscriberTo();
}
3.下面是订阅者的方法,首先实现在订阅的类中注册订阅者,以post为例
RxBus.getInstance().register(this);
//订阅者中注册事件
RxBus.getInstance().register(this);
关键代码 :
下面在RxBus.getInstance().register(this) 初始化的时候,会扫描该类中有自定义注解的方法,有该注解表示这个方法就是我们需要通过动态代理所代理的主题
public void register(@NonNull Object tag){
//控制反转,和ButtonKniffer底层源码相似的逻辑
injectSubscriber(tag);
}
private void injectSubscriber(final Object tag) {
try {
//拿到订阅者类
Class<?> clazz = tag.getClass();
//反射得到所有方法
Method[] methods = clazz.getDeclaredMethods();
for(Method method : methods){
//拿到方法上的注解
Annotation[] annos = method.getAnnotations();
for(Annotation anno : annos) {
Class<?> annoType = anno.annotationType();
EventBase eventAnno = annoType.getAnnotation(EventBase.class);
//如何这个方法没有EvenBase注解,则continue,这里拿到所有包含EventBase也就是我们自定义注解的方法
if (eventAnno == null) {
continue;
}
//获取回调三要素
String listenerSetter = eventAnno.listenerSetter();
Class<?> listenerType = eventAnno.listenerType();
String subscriberMethodName = eventAnno.callBackMethod();
//拿到注解中的标记值
Method valueMtd = annoType.getDeclaredMethod("subscriberTo");
String subscriberTag = (String) valueMtd.invoke(anno);
final Map<String, Method> mtdMap = new HashMap<>();
mtdMap.put(subscriberMethodName, method);
Subject subject;
List<Subject> subjectList = subjectMap.get(subscriberTag);
if (subjectList == null) {
subjectList = new ArrayList<>();
subjectMap.put(subscriberTag, subjectList);
}
//拿到PublishSubject,从集合中拿到的,这个subjectMap集合用来保存发送者发送的subject
subject = PublishSubject.create();
subjectList.add(subject);
Method setterListenerMethod = subject.getClass().getMethod(listenerSetter, listenerType);
//获取代理对象,代理对象实现了Action.class
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}
, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String mtdName = method.getName();
//获取用户自定的方法 mtdName = call mtd = progressUpdate
Method mtd = mtdMap.get(mtdName);
if (mtd != null) {
return mtd.invoke(tag, args);
}
return null;
}
});
//执行 subscriber(new Proxy { public void call })
setterListenerMethod.invoke(subject,proxy);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
最后再看一下post方法, 这里发送subject并保存到subjectMap集合中
private Map<Object, List<Subject>> subjectMap = new HashMap();
public void post(@NonNull Object tag, @NonNull Object content) {
List<Subject> subjectList = subjectMap.get(tag);
if (subjectList != null) {
if (subjectList.size() != 0) {
for (Subject subject : subjectList) {
subject.onNext(content);
}
}
}
}
这样就实现了想要达到的效果,让我们跑一下看看效果
在需要发送的地方调用
在我们需要注册订阅者的地方
注册方法 :
看下最终的log,点击开始下载,retrofit返回进度条监听,RxBus通过IOC 将回调信息发送给订阅者 :
最后补充 : 该文章只是通过PublishSubject实现了post方法, 关于ReplaySubject方法实现postSticky 大家可以自己试着实现以下,也可以通过自定义注解实现线程调度,在方法上加一个@observeOn(调度器) 来实现线程控制, 本文主要想要表达的是一种封装思想,通过IOC+动态代理 还可以完成很多种业务封装.