这也许是 RxBus 最友好的文章
open_
8年前
<p>本人之前做过一款社交类软件,需要让“未读消息数”实时显示到应用程序内(类似QQ,微信)。没做过社交软件的同学也许会认为这个功能很好做,其实不然。这个功能虽然逻辑上简单,但是实际操作起来会很麻烦。我来简单描述一下这个需求到底有多麻烦:如果一个好友给你发了一条消息,而此时你的应用程序不在前台,但是没办法,这个时候你必须要去更新一下未读消息数角标。那怎么办呢???又或者,你接收到新消息之后,你当前所处的线程并非UI主线程,在这种情况下,你还是要去更新UI,会显得十分尴尬。不是说子线程不能去更新UI,而是在子线程操作UI会十分麻烦,冗余代码也会很多。</p> <p>遇到这种需要不断去更新UI的情况,基本上会采取以下三种做法。</p> <p>做法一:很多同学都会开启一个后台service,并持有MainActivity的引用,每次接收到消息,就让service去调用MainActivity的相关方法,更新MainActivity的UI,与此同时,再new一个Notification出来,告诉用户,新消息来了。</p> <p>做法二:可能大部分人都会想到这个做法,就是用一个BoradcastReceiver去接收新消息,然后再把消息的全部内容封装到一个intent里面去,通过content.startActivity(intent)方法把intent传到对应的Activity。对应的Activity接收到Intent之后,会去解析这个intent。这个做法相对于“做法一”会灵活很多。</p> <p>做法三:在需要更新UI的地方定义一个接口,然后每次更新UI的时候都去调用这个接口。这个做法也是可取的,但是一不小心就出现内存泄漏的问题。</p> <p>如果是我的话,更倾向于第二种做法,为什么呢?因为比较灵活,而且不容易发生内存泄漏的问题。</p> <p>当时我也是确实是这么做的,但是我思考了几天,发现这么做不是很好,但是说不出一个理由来。。。。。。可能是因为这种做法代码比较多,而且不容易被别人读懂吧</p> <p>查了几天资料,发现用EventBus/otto/RxBus可以很好的解决这个问题,综合它们的优缺点,我最终选择了RxBus。</p> <p>RxBus主要用来处理应用程序间各个组件的通信,或者组件与组建之间的数据传递。(不用再像BroadcastReceiver一样,把数据封装到intent里面再传递出去了)</p> <p>首先,为什么叫他RxBus?其实我也不知道,当初我以为RxBus是一个封装好的库,直接拿过来用就好了。事实并非如此,我找了半天没找到现成的库,都是教你如何去通过RxJava来构造一个RxBus,既然这样,那我也就跟着他们做了。其实说到底,RxBus学的是一种思路,而并不是给你一个现成的库,然后直接去调用。好了,废话不多说,开始我们今天的任务吧!</p> <p>对RxJava还不熟悉的同学请绕道,以下内容针对有RxJava基础的同学。</p> <p>RxJava内部实现其实还蛮复杂的,我暂时没有时间去看源码,但是思路还是蛮简单的,就是观察者模式。观察者模式我在之前的文章也写过,还不太清楚的同学可以去看一下我的另外一篇文章 《超详细:常用的设计模式汇总》 。那我们如何去巧妙地去使用RxJava,实现一个Rxbus呢?看一下我的代码。</p> <pre> <code class="language-java">public enum RxBus { INSTANCE; private Subject<Object, Object> mRxBusObserverable = new SerializedSubject<>(PublishSubject.create()); public static void send(Object object) { if (INSTANCE.mRxBusObserverable.hasObservers()) { INSTANCE.mRxBusObserverable.onNext(object); } } public static Observable<Object> toObserverable() { return INSTANCE.mRxBusObserverable; } }</code></pre> <p>我直接写成了一个单例(单例模式写法很多,不要纠结于我的这种写法,你们完全可以换一种写法),然后里面有两个方法,一个是send,一个是toObserable。send方法很简单,就是用来发送一条消息的,消息类型是Object类型,其实也就是说,可以发送任何消息。那么toObserable是干嘛的呢?就是用来订阅消息的,如果不订阅的话,send方法就没有任何意义了。就好比一个女生,没有一个男生去关注她,她还在那里搔首弄姿的话,显得很白痴!</p> <p>send方法是static类型的,所以在程序的任何地方都可以调用。调用之后,代表一条信息发出去了,只要是订阅过的人,都可以接收到。</p> <p>toObserverable也是static类型的,所以在程序的任何地方都可以调用。调用之后,就说明订阅成功了。如果我这个时候send一下,那就会接收到消息了。</p> <p>说再多都是屁!那么我们来看一下如何使用这个类。</p> <p>我们先调用toObserverable这个方法,还是那句话,如果不订阅的话,send就没有意义了。那我们什么时候去订阅呢?谁去订阅呢?订阅之后要干嘛呢?</p> <p>看代码便知:</p> <pre> <code class="language-java">public abstract class BaseActivity extends AppCompatActivity { private Subscription subscription; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initRxBus(setOnNext()); } @Override protected void onDestroy() { super.onDestroy(); unbindRxBus(); } private Action1<Object> setOnNext() { return new Action1<Object>() { @Override public void call(Object o) { doOnNext(o); } }; } protected abstract void doOnNext(Object o); private void initRxBus(final Action1<Object> onNext) { if (onNext != null) { subscription = RxBus.toObserverable() .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(onNext); } } private void unbindRxBus() { if (subscription != null) { subscription.unsubscribe(); } } }</code></pre> <p>自认为这段代码理解起来还是蛮有意思的。从整体上来看,onCreate方法初始化RxBus,onDestory方法解绑RxBus。换句话说,其实就是在onCreate中订阅一下,在onDestory中取消订阅,意思就是说,MainActivity已经开始关注RxBus了,RxBus只要一有动静,MainActivity就能马上知道。取消订阅是为了防止程序内存泄漏。取消订阅的代码很简单,我不多解释。我们主要来看一下订阅的代码。</p> <p>initRxBus(setOnNext());</p> <p>先看一下initRxBus(final Aciton1<Object> onNext)这个方法,其实里面就是简单调用了RxBus的订阅方法,然后用Schedulers切换了线程,最后subscribe一下。接触过RxJava的同学,这段代码理解起来应该不是很费劲。</p> <p>再来看一下setOnNext( )方法,返回值是Action1,而里面的代码我就直接return了一个Action1,但是我把call(Object o)用一个抽象方法分出去了,这么做有一个好处,就是代码简洁一些。这里面可能有一点绕,大家还是得多看看。那这个方法到底是干嘛用的???肯定有用啊,不然写出来干嘛!!!如果你这个时候调用一下RxBus.send方法,你会发现,doOnNext是会被执行的,而且参数o就是send方法传过来的参数。这么搞的话,是不是比BroadcastReceiver简单多了!如果你还没明白,那我们来写个例子吧。</p> <p>这个BaseActivity是抽象的,所以我们再写一个Activity来继承它,看一下我是怎么写的。</p> <pre> <code class="language-java">public class MainActivity extends BaseActivity { private final String TAG = MainActivity.class.getSimpleName(); @Override protected void doOnNext(Object o) { //接收到全局广播之后在这里处理相关的业务逻辑 Log.d(TAG, "订阅者:" + TAG + "->接收到的消息:" + o.toString()); } } public class MainActivity2 extends BaseActivity { private final String TAG = MainActivity2.class.getSimpleName(); @Override protected void doOnNext(Object o) { //接收到全局广播之后在这里处理相关的业务逻辑 Log.d(TAG, "订阅者:" + TAG + "->接收到的消息:" + o.toString()); } }</code></pre> <p>我写了两个Activity,其他不相关的代码我已经删掉了,所以代码看起来很简洁。这两个Activity都对doOnNext进行了处理。这个时候我们要定义一个按钮,当点击按钮的时候,就发送一条信息,这个按钮的点击事件是这么写的:</p> <pre> <code class="language-java">@Override public void onClick(View v) { RxBus.send(new Random().nextInt()); }</code></pre> <p>代码很简单,就是发送一个随机的数字。现在万事俱备,只欠东风,我们要让程序跑起来。怎么跑呢?我是这么操作的:</p> <pre> <code class="language-java">public class MainActivity extends BaseActivity { private final String TAG = MainActivity.class.getSimpleName(); @Override protected void doOnNext(Object o) { //接收到全局广播之后在这里处理相关的业务逻辑 Log.d(TAG, "订阅者:" + TAG + "->接收到的消息:" + o.toString()); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); …… …… …… //启动MainActivity2 Intent intent = new Intent(this, MainActivity2.class); startActivity(intent); } }</code></pre> <p>进入到MainActivity之后,立马启动MainActivity2,这样就能保证两个Activity同时存在了。我们这个时候再来看一下MainActivity2的代码:</p> <pre> <code class="language-java">public class MainActivity2 extends BaseActivity { private final String TAG = MainActivity.class.getSimpleName(); @Override protected void doOnNext(Object o) { //接收到全局广播之后在这里处理相关的业务逻辑 Log.d(TAG, "订阅者:" + TAG + "->接收到的消息:" + o.toString()); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); …… …… …… findViewById(R.id.button1).setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { RxBus.send(new Random().nextInt()); } }); } }</code></pre> <p>代码也很简单,就是每次点击按钮的时候,都会发送一个随机数字,只要是订阅过的人,都可以接收到这条消息。我运行了一下程序,看一下运行结果:</p> <p><img src="https://simg.open-open.com/show/0af6361da9eb765fc9bbcb2712cf3862.jpg"></p> <p>114.png</p> <p>可以看到,两个订阅者都收到消息了。那我们现在尝试去更新一下UI,代码要稍微改动一下,改成这样:</p> <pre> <code class="language-java">public class MainActivity extends BaseActivity { private TextView rxbus_tv; @Override protected void doOnNext(Object o) { if (rxbus_tv != null) { rxbus_tv.setText("接收到消息->" + o.toString()); } } @Override protected void onCreate() { …… …… …… rxbus_tv = (TextView) findViewById(R.id.activity_rxbus1_tv); findViewById(R.id.activity_rxbus1_btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(context, TestRxActivity2.class); startActivity(intent); } }); } }</code></pre> <pre> <code class="language-java">public class MainActivity2 extends BaseActivity { private TextView rxbus_tv; @Override protected void doOnNext(Object o) { if (rxbus_tv != null) { rxbus_tv.setText("接收到消息->" + o.toString()); } } @Override protected void onCreate() { …… …… …… rxbus_tv = (TextView) findViewById(R.id.activity_rxbus2_tv); findViewById(R.id.activity_rxbus2_btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { RxBus.send(new Random().nextInt()); } }); } }</code></pre> <p>没有太多的改动,就是在doOnNext方法里面把Log打印语句换成了setText。看一下运行效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d21738e0fb9d96430b13fc631fff06cf.gif"></p> <p>rxbus.gif</p> <p>每当我在MainActivity2中发送一条消息的时候,MainActivity1和MainActivity2都可以接收到并且能够去更新UI,是不是感觉很方便。</p> <p>那么这个时候有的同学就要问了,我只想让MainActivity1去接收我的消息并且更新UI,我不想让MainActivity2去更新,怎么办呢?</p> <p>其实这个问题很好解决的。我们只需要在相应的Activity中加一些过滤条件就好了,问题是,怎么加过滤条件呢?</p> <p>这里我先封装一个bean对象,包含三个属性,一个是from,一个是to,还有一个是content,具体代码是这样的:</p> <pre> <code class="language-java">public class RxBusBean { private String from; private String to; private String content; public RxBusBean() { } public RxBusBean(String from, String to, String content) { this.from = from; this.to = to; this.content = content; } public String getFrom() { return from; } public void setFrom(String from) { this.from = from; } public String getTo() { return to; } public void setTo(String to) { this.to = to; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }</code></pre> <p>其实理解起来就像寄快递一样,有寄货方姓名,收货方姓名和具体内容。</p> <p>使用的时候可以封装成Gson,这样比较简单。发送消息的时候代码要改成这样:</p> <pre> <code class="language-java">RxBusBean bean = new RxBusBean(); bean.setFrom("MainActivity2"); bean.setTo("MainActivity1"); bean.setContent(new Random().nextInt() + ""); Gson gson = new Gson(); RxBus.send(gson.toJson(bean));</code></pre> <p>接收消息的时候要改成这样:</p> <pre> <code class="language-java">Gson gson = new Gson(); RxBusBean bean = gson.fromJson(o.toString(), RxBusBean.class); if (bean.getTo().equals("MainActivity1")) { rxbus_tv.setText("接收到消息->" + bean.getContent()); }</code></pre> <p>这样处理之后,只有MainActivity1可以对消息进行处理。虽然MainActivity2可以接收到消息,但是没办法对消息进行处理。看一下效果图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9ae55554b11b98c36d1f5f6b4868e7d1.gif"></p> <p>看到没有,已经达到我们想要的效果了。:smile::smile::smile:</p> <p> </p> <p> </p> <p>来自:http://www.jianshu.com/p/ebaa10b18963</p> <p> </p>