使用Rxjava自己创建RxBus
https://piercezaifman.com/how-to-make-an-event-bus-with-rxjava-and-rxandroid/
https://lingyunzhu.github.io/2016/03/01/RxBus%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8F%8A%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/
https://lingyunzhu.github.io/2016/03/01/RxBus的实现及简单使用
https://blog.kaush.co/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/ (这篇文章里的考虑不全,尽量不要使用)
因为Rx在调用了oncomplete或onerror之后,或者在 onnext里抛出了异常之后,bus就失效了,因此有必要使用rxrelay来解决这个问题,而且rxrelay是大神JakeWharton开发的:
rxrelay: https://github.com/JakeWharton/RxRelay
下面这个rxBus是基于RxRelay来开发的一个事件总线库:
https://github.com/JohnnyShieh/RxBus
至于为啥要在事件总线上使用rxrelay,这里有个很好的解释:
url: https://github.com/JakeWharton/RxRelay/issues/7
why subscribing a Subject or Relay to a stream should be immediately an anti-pattern ? i'll try to give a simple example.
suppose we have separate presenters.
P1 holds a View with a Button.
P2 hold a view to display info. Scenario : No Relays P1 receives call to send Network Request
P1 sends Request and gets an Observable form the Network Module
P1 send the observable to P2
P2 subscribes to it
P2 receives an event and renders data
... Scenario : With Relays
) P2 subscribes to a Relay "XX" (happens only once) P1 receives call to send Network Request
P1 sends Request and gets an Observable form the Network Module
P1 subscribes Relay "XX" to the Observable
P2 receives an event and renders data
... Sure, here we do not have that much advantage especially that Relays/Subjects are the vulnerable(mutable) part in the chain, but lets suppose we take care of that. Now what if we have many P1 and many P2 it can become a mesh of passing Observables around and this doesn't look too reactive to me.
How to make an Event Bus with RxJava and RxAndroid
21 January 2017
If you’ve ever needed to communicate between different parts of your application, it can be painful. To alleviate this, you can use an event bus like Otto. But, with the addition of RxJava and RxAndroid to the Android ecosystem you don’t need to rely on Otto anymore. Otto is actually deprecated in favour of these newer libraries, since making your own event bus with them is actually quite easy.
I came up with my own solution that works well for my purposes. You can use it as is if you want, or tweak it to fit your needs.
First attempt
If you just want to pass arbitrary data around your app this is all you need:
public final class RxBus { private static PublishSubject<Object> sSubject = PublishSubject.create(); private RxBus() {
// hidden constructor
} public static Disposable subscribe(@NonNull Consumer<Object> action) {
return sSubject.subscribe(action);
} public static void publish(@NonNull Object message) {
sSubject.onNext(message);
}
} //Example usage below: public class MyActivity extends Activity { @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity); //Using Retrolambda
RxBus.subscribe((message) -> {
if (message instanceof Data) {
Data data = (Data) cityObject;
Log.v("Testing", data.getInfo());
}
});
}
} public class MyFragment extends Fragment { @Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment, container, false); //Using Retrolambda
v.findViewById(R.id.view).setOnClickListener(view -> {
RxBus.publish(new Data("Hello World"));
}); return v;
} }
Using a PublishSubject means that a new subscriber will only receive events emitted after they subscribed. It will not repeat old events to new subscribers.
This simple solution has a few problems though. You can’t pick what kind of events your subscriber will receive, it’s going to see everything. The only way to ensure you get the data you want, is to define a new class for each event, such as XYZDataDownloadedEvent
or PurpleButtonClickedEvent
. Then you have to do an instanceof
check and cast it. Personally, I don’t like having to create a new class for each type of event I want to broadcast.
Also, this solution can cause memory leaks if you don’t unsubscribe from each subscription. Ideally I want to be able to publish an event and subscribe to updates for that event. I don’t want to have to manage subscriptions in each place I subscribe.
Second attempt
To address these issues, my next iteration looked like this:
public final class RxBus { private static Map<String, PublishSubject<Object>> sSubjectMap = new HashMap<>();
private static Map<Object, CompositeDisposable> sSubscriptionsMap = new HashMap<>(); private RxBus() {
// hidden constructor
} /**
* Get the subject or create it if it's not already in memory.
*/
@NonNull
private static PublishSubject<Object> getSubject(Integer subjectKey) {
PublishSubject<Object> subject = sSubjectMap.get(subjectKey);
if (subject == null) {
subject = PublishSubject.create();
subject.subscribeOn(AndroidSchedulers.mainThread());
sSubjectMap.put(subjectKey, subject);
} return subject;
} /**
* Get the CompositeDisposable or create it if it's not already in memory.
*/
@NonNull
private static CompositeDisposable getCompositeDisposable(@NonNull Object object) {
CompositeDisposable compositeDisposable = sSubscriptionsMap.get(object);
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
sSubscriptionsMap.put(object, compositeDisposable);
} return compositeDisposable;
} /**
* Subscribe to the specified subject and listen for updates on that subject. Pass in an object to associate
* your registration with, so that you can unsubscribe later.
* <br/><br/>
* <b>Note:</b> Make sure to call {@link RxBus#unregister(Object)} to avoid memory leaks.
*/
public static void subscribe(@Subject int subject, @NonNull Object lifecycle, @NonNull Consumer<Object> action) {
Disposable disposable = getSubject(subject).subscribe(action);
getCompositeDisposable(lifecycle).add(disposable);
} /**
* Unregisters this object from the bus, removing all subscriptions.
* This should be called when the object is going to go out of memory.
*/
public static void unregister(@NonNull Object lifecycle) {
//We have to remove the composition from the map, because once you dispose it can't be used anymore
CompositeDisposable compositeDisposable = sSubscriptionsMap.remove(lifecycle);
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
} /**
* Publish an object to the specified subject for all subscribers of that subject.
*/
public static void publish(Integer subject, @NonNull Object message) {
getSubject(subject).onNext(message);
}
}
I added a CompositeDisposable to manage all of the subscriptions for a given object (typically an Activity or Fragment). I also added a map to keep track of the different types of subjects. That way, I can easily keep track of all of the subscriptions and subjects.
Also, to simplify management of unsubscribing and keeping a reference to those subscriptions, I created a BaseActivity and BaseFragment.
public abstract class BaseActivity extends AppCompatActivity { @Override
protected void onDestroy() {
super.onDestroy();
RxBus.unregister(this);
}
}
public abstract class BaseFragment extends Fragment { @Override
public void onDestroy() {
super.onDestroy();
RxBus.unregister(this);
}
}
Calling RxBus.unregister(this)
in onDestroy()
is all that’s required for cleanup. If there are subscriptions associated to that object they will be unsubscribed and removed. I also wrote comments to make it clear that if you subscribe, you need to call unregister. In case you aren’t using a base class that handles it or subscribe to the bus from somewhere else.
Whenever I create a new subject, it’s set to be subscribed on the main thread. For my purposes, all of the events being posted will trigger UI updates. You could always extend it to allow for subscribing on different threads if you want. The current implementation makes it simple and covers the majority of use cases.
Final implementation
The last change I made is to how you define what subject you are subscribed to. Initially you could just pass in a String key, defined wherever you like. I like having all these keys organized in one place though. Also, I wanted to limit the events you could subscribe and publish to. So, I changed the String parameter to an int, and created a set of integer constants with the IntDef annotation. You can see my completed RxBus.java class below:
/**
* Used for subscribing to and publishing to subjects. Allowing you to send data between activities, fragments, etc.
* <p>
* Created by Pierce Zaifman on 2017-01-02.
*/ public final class RxBus { private static SparseArray<PublishSubject<Object>> sSubjectMap = new SparseArray<>();
private static Map<Object, CompositeDisposable> sSubscriptionsMap = new HashMap<>(); public static final int SUBJECT_MY_SUBJECT = 0;
public static final int SUBJECT_ANOTHER_SUBJECT = 1; @Retention(SOURCE)
@IntDef({SUBJECT_MY_SUBJECT, SUBJECT_ANOTHER_SUBJECT})
@interface Subject {
} private RxBus() {
// hidden constructor
} /**
* Get the subject or create it if it's not already in memory.
*/
@NonNull
private static PublishSubject<Object> getSubject(@Subject int subjectCode) {
PublishSubject<Object> subject = sSubjectMap.get(subjectCode);
if (subject == null) {
subject = PublishSubject.create();
subject.subscribeOn(AndroidSchedulers.mainThread());
sSubjectMap.put(subjectCode, subject);
} return subject;
} /**
* Get the CompositeDisposable or create it if it's not already in memory.
*/
@NonNull
private static CompositeDisposable getCompositeDisposable(@NonNull Object object) {
CompositeDisposable compositeDisposable = sSubscriptionsMap.get(object);
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
sSubscriptionsMap.put(object, compositeDisposable);
} return compositeDisposable;
} /**
* Subscribe to the specified subject and listen for updates on that subject. Pass in an object to associate
* your registration with, so that you can unsubscribe later.
* <br/><br/>
* <b>Note:</b> Make sure to call {@link RxBus#unregister(Object)} to avoid memory leaks.
*/
public static void subscribe(@Subject int subject, @NonNull Object lifecycle, @NonNull Consumer<Object> action) {
Disposable disposable = getSubject(subject).subscribe(action);
getCompositeDisposable(lifecycle).add(disposable);
} /**
* Unregisters this object from the bus, removing all subscriptions.
* This should be called when the object is going to go out of memory.
*/
public static void unregister(@NonNull Object lifecycle) {
//We have to remove the composition from the map, because once you dispose it can't be used anymore
CompositeDisposable compositeDisposable = sSubscriptionsMap.remove(lifecycle);
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
} /**
* Publish an object to the specified subject for all subscribers of that subject.
*/
public static void publish(@Subject int subject, @NonNull Object message) {
getSubject(subject).onNext(message);
}
}
Potential issues
While working on this, I made a list of problems I had with the implementation. Some of which I believe can be addressed, others I’m not sure.
I’m still passing around objects which have to be cast to the correct type. I’m not sure if there’s a way around this, because the subject publishes Objects
. So, the subscriber will only receive Objects
.
You can pass in any object to associate your subscription with, so there’s no guarantee that you’ve actually unsubscribed. I tried to address this with my comments, saying that you must call unregister. But there’s no guarantee that it gets called, which will cause memory leaks.
The BaseActivity and BaseFragment unregister from the bus in onDestroy()
. This means that if you start a new activity, the old activity will still be subscribed. So if you publish an event that the previous activity is subscribed to, it may end up causing your app to crash with java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
. I didn’t want to call unregister in onStop()
because if you go back to the previous activity, it won’t be subscribed anymore. If you are careful with how you manage your subjects, this won't be an issue. Ideally the subscriptions would pause and resume with the lifecycle, and finally get destroyed with the lifecycle.
Lastly, I’m using static members instead of the singleton pattern. Technically I believe that using the singleton pattern is more memory efficient. Since it will only create the class when it needs to. But, in my case since I’m using RxBus in onCreate()
for most of my activities, it won’t really save anything. Plus, the amount of memory it uses is negligible. Some people also think that static variables are evil.
Final thoughts
This solution isn’t perfect, but I felt it was a good compromise between complexity and ease of use.
注意上面那篇文章没有考虑到线程安全,请结合如下示例使用:
一、添加RxJava和RxAndroid依赖
//RxJava and RxAndroid
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
二、新建RxBus类
不多说直接上代码:
import rx.Observable;
import rx.subjects.PublishSubject;
import rx.subjects.SerializedSubject;
import rx.subjects.Subject; /**
* Created by xialo on 2016/6/28.
*/
public class RxBus { private static volatile RxBus mInstance; private final Subject bus;
{
bus = new SerializedSubject<>(PublishSubject.create());
} /**
* 单例模式RxBus
*
* @return
*/
public static RxBus getInstance()
{ RxBus rxBus2 = mInstance;
if (mInstance == null)
{
synchronized (RxBus.class)
{
rxBus2 = mInstance;
if (mInstance == null)
{
rxBus2 = new RxBus();
mInstance = rxBus2;
}
}
} return rxBus2;
} /**
* 发送消息
*
* @param object
*/
public void post(Object object)
{ bus.onNext(object); } /**
* 接收消息
*
* @param eventType
* @param <T>
* @return
*/
public <T> Observable<T> toObserverable(Class<T> eventType)
{
return bus.ofType(eventType);
}
}
1、Subject同时充当了Observer和Observable的角色,Subject是非线程安全的,要避免该问题,需要将 Subject转换为一个 SerializedSubject,上述RxBus类中把线程非安全的PublishSubject包装成线程安全的Subject。
2、PublishSubject只会把在订阅发生的时间点之后来自原始Observable的数据发射给观察者。
3、ofType操作符只发射指定类型的数据,其内部就是filter+cast
三、创建你需要发送的事件类
我们这里用StudentEvent举例
/**
* Created by xialo on 2016/6/28.
*/
public class StudentEvent {
private String id;
private String name; public StudentEvent(String id, String name) {
this.id = id;
this.name = name;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
四、发送事件
RxBus.getInstance().post(new StudentEvent("007","小明"));
五、接收事件
rxSbscription=RxBus.getInstance().toObserverable(StudentEvent.class)
.subscribe(new Action1<StudentEvent>() {
@Override
public void call(StudentEvent studentEvent) {
textView.setText("id:"+ studentEvent.getId()+" name:"+ studentEvent.getName());
}
});
注:rxSbscription是Sbscription的对象,我们这里把RxBus.getInstance().toObserverable(StudentEvent.class)赋值给rxSbscription以方便生命周期结束时取消订阅事件
六、取消订阅
@Override
protected void onDestroy() {
if (!rxSbscription.isUnsubscribed()){
rxSbscription.unsubscribe();
}
super.onDestroy();
}
参考:
http://wuxiaolong.me/2016/04/07/rxbus/
http://www.jianshu.com/p/ca090f6e2fe2
使用Rxjava自己创建RxBus的更多相关文章
- RxJava【创建】操作符 create just from defer timer interval MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- RXJAVA之创建被观察者
RXJava中提供了多种创建数据源的方式 使用create方法 Observable<String> observable = Observable.create(new Observab ...
- rxjava封装,RxBus封装(上线项目集成,声明周期管理,处理溢出内存,支持同时多个请求。)
Github地址 RxLibrary工程:1.rxjava2 + retrofit2的封装,常用的请求(Get,Post,文件上传,文件下载),防止内存泄漏,简单便捷,支持自定义loading等属性. ...
- 一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库 RxJava,相当好
https://github.com/ReactiveX/RxJava https://github.com/ReactiveX/RxAndroid RX (Reactive Extensions,响 ...
- 《Android进阶之光》--RxJava实现RxBus
事件总线RxBus,替代EventBus和otto 1)创建RxBus public class RxBus{ private static volatile RxBus rxBus; private ...
- Android 打造属于自己的RxBus
RxBus 通过RxJava实现Rxbus. 相信大家已经非常熟悉EventBus了.最近正在学习Rxjava,如果在项目中已经使用了Rxjava,使用RxBus来代替EventBus应该是不错的选择 ...
- 清晰易懂的RxJava入门实践
导入 我相信大家肯定对ReactiveX 和 RxJava 都不陌生,因为现在只要是和技术相关的网站,博客都会随处见到介绍ReactiveX和RxJava的文章. ReactiveX Reactive ...
- Android MVP+Retrofit+RxJava实践小结
关于MVP.Retrofit.RxJava,之前已经分别做了分享,如果您还没有阅读过,可以猛戳: 1.Android MVP 实例 2.Android Retrofit 2.0使用 3.RxJava ...
- 82.Android之MVP+Retrofit+RxJava实践小结
转载:http://wuxiaolong.me/2016/06/12/mvpRetrofitRxjava/ 关于MVP.Retrofit.RxJava,之前已经分别做了分享,如果您还没有阅读过,可以猛 ...
随机推荐
- Android学习笔记——Menu(三)
知识点 今天继续昨天没有讲完的Menu的学习,主要是Popup Menu的学习. Popup Menu(弹出式菜单) 弹出式菜单是一种固定在View上的菜单模型.主要用于以下三种情况: 为特定的内容提 ...
- MathType公式波浪线怎么编辑
数学公式中有很多符号与数学样式,在用手写时是没有问题的,但是很多论文或者期刊中也是需要用到这些符号或者样式的,比如公式波浪线,那么MathType公式波浪线怎么编辑出来呢? 具体操作步骤如下: 1.打 ...
- Win7配置SVN详细步骤(服务器和客户端)
下载并安装服务器端SVN VisualSVN Server 下载并安装客户端SVN TortoiseSVN 创建SVN库 在C盘创建文件夹MySVN(可自由命名),打开文件夹----右键Torto ...
- mysql通过mysql_install_db初始化数据目录时使用--user选项的作用是什么?
需求描述: mysql数据库通过mysql_install_db初始化数据目录时,使用了--user选项,这里记录下该参数的作用 参数解释: 1.--user的作用:就是以哪个操作系统用户来执行mys ...
- [深入理解Android卷一全文-第四章]深入理解zygote
由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的所有内容. ...
- Redis(六)-- SpringMVC整合Redis
一.pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www ...
- Python 收集主机信息
写一个 python 脚本,收集以下信息 ( CentOS 6 ) : IP地址 <ip> 主机名 <hostname> 操作系统版本 <osver> ...
- tableView删除功能小记
由于项目需要,做一个UITableView来实现删除功能. 效果如图: 功能思路其实不难: 交代一下,我自己要实现的效果: 1.TableView是分组的. 2.点击删除按钮后,某行被删除. 写完 ...
- Django学习笔记 Django的工程目录
mysite├── manage.py 管理项目:包括数据库建立.服务器运行.测试……└── mysite ├── __init__.py ├── settings.py 配置文件:应用 ...
- c++11——std::function和bind绑定器
c++11中增加了std::function和std::bind,可更加方便的使用标准库,同时也可方便的进行延时求值. 可调用对象 c++中的可调用对象存在以下几类: (1)函数指针 (2)具有ope ...