Rxbus结合dagger2作用域实现局部单例(资源自动回收和事件独立)

原由

之前使用Rxjava2实现的单例模式的Rxbus来进行组件通信

现在有两个问题不好实现:

  • 在不同的Activity中同时post出两个相同的Class类型,只想在当前Activity中接收该类型事件
  • 实现订阅事件后产生的Disposable统一管理回收(使用者不用考虑回收问题,在当前Activity产生的Disposable自动在onDestroy()后被回收)

之后是为什么会产生上面两种需求的场景

最近在使用MVP模式封装Retrofit网络访问模块时,需要从Model层回调给Presenter层再回调给View层,虽然解耦很强,但作为喜欢偷懒的程序员,总觉得这么写很费劲.
于是乎,就思考如何简化代码而又不影响解耦程度

经过一天的思考和设计,得到以下方案:

  • 去除Presenter层(使用Rxbus代替其功能)
  • View层接口去除,接收事件改造成订阅事件
  • Model层功能不变,回调数据给Presenter层改为发送事件给订阅者(View层)

仔细想了想,发现这个方案不仅没有增加耦合度,还精简了一半的代码,而其中最重要的就是通过Rxbus来进行事件的订阅和发送

注:这种方式实现MVP模型的方式放在下一篇文章来说,这篇文章主要讲一讲结合dagger2作用域实现局部单例

3501495-a758dc63cd78364a.png

备注:图画的有点丑.别介意!

实现

首先写出dagger2的Component和Module

//这里使用单例域
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    //这里暴露子组件
    BaseComponent baseComponent(BaseModule baseModule);
}

@Singleton
@Module
public class AppModule {
    private App mApp;
    public AppModule(App app) {
        mApp = app;
    }

    /**
     * 将App提供出去
     * @return
     */
    @Singleton
    @Provides
    App providerApp() {
        return mApp;
    }
}

以上是单例域的Component域Module,在App中注册,因应用只有一个Application,所以在这个域中提供的@Singleton注解的对象都将只有一份(也就是单例效果)

之后是Activity域的dagger组件

//这里是针对BaseActivity,所有继承BaseActivity的Activity都在BaseScope域下,但这些子类之间却互不影响,各自维持一个BaseScope
//因为每个子类都会在父类的onCreate()方法中创建一个BaseComponent,虽然都在BaseScope下,但却被两个不同的BaseComponent对象分别管理
//所以BaseModule所提供的对象都只在当前Activity中保持单例
@BaseScope
@Subcomponent(modules = BaseModule.class)
public interface BaseComponent {
    void inject(MainActivity activity);

    void inject(Main2Activity activity);
}

@BaseScope
@Module
public class BaseModule {
    private Activity mActivity;
    private CompositeDisposable mDisposable = new CompositeDisposable();

    public BaseModule(Activity activity) {
        mActivity = activity;
    }

    @BaseScope
    @Provides
    Activity providerActivity() {
        return mActivity;
    }

    @BaseScope
    @Provides
    Context providerContext() {
        return mActivity;
    }

    /**
     * CompositeDisposable,这里使用了BaseScope域,则在此域中的mDisposable对象只有一份(也就是局部单例)
     * @return
     */
    @BaseScope
    @Provides
    CompositeDisposable providerDisposable() {
        return mDisposable;
    }
}

dagger的组件都搭建完毕,此时就该正主上场了
在这里将实现一个非单例的Rxbus,通过dagger作用域来实现局部单例,这也就会使发送的事件只会在局部被接收,并通过事件管理器来自动回收资源

//使Rxbus的作用域为Activity域,保证其在该域中,也就是Activity中保证单例(本例中作用域被限制在了Activity中,可根据情况更改)
@ActivityScope
public class RxBus {
    private final FlowableProcessor<Object> mBus;
    //将Rxbus注入
    @Inject
    public RxBus() {
        //调用toSerialized()方法,保证线程安全
        mBus = PublishProcessor.create().toSerialized();
    }

    /**
     * 发送消息
     * @param o
     */
    public void post(Object o) {
        new SerializedSubscriber<>(mBus).onNext(o);
    }

    /**
     * 确定接收消息的类型
     * @param aClass
     * @param <T>
     * @return
     */
    public <T> Flowable<T> toFlowable(Class<T> aClass) {
        return mBus.ofType(aClass);
    }

    /**
     * 判断是否有订阅者
     * @return
     */
    public boolean hasSubscribers() {
        return mBus.hasSubscribers();
    }

}

然后是事件管理器和事件包裹类

@BaseScope
public class SubstribeManager {
    @Inject
    RxBus mRxBus;
    //这里得到的CompositeDisposable与Activity中的是同一个对象
    @Inject
    CompositeDisposable mDisposable;
    @Inject
    public SubstribeManager() {
    }

    /**
     * 发送事件
     * @param o
     */
    public void post(Object o) {
        mRxBus.post(o);
    }

    /**
     * 订阅事件,返回包裹类,方便同一回收Disposable
     * @param aClass
     * @param <T>
     * @return
     */
    public <T> FlowableWrap<T> subscribeResult(Class<T> aClass) {
        return new FlowableWrap<>(mRxBus.toFlowable(aClass),mDisposable);
    }

}

public class FlowableWrap<T> {
    private Flowable<T> mFlowable;
    private CompositeDisposable mDisposable;

    public FlowableWrap(Flowable<T> flowable, CompositeDisposable disposable) {
        mFlowable = flowable;
        mDisposable = disposable;
    }

    /**
     * 订阅事件,与Rxjava一致,自动添加事件到CompositeDisposable中,方便回收
     */
    public void subscribe(final Consumer<T> consumer) {
        mDisposable.add(mFlowable.subscribe(new Consumer<T>() {
            @Override
            public void accept(@NonNull T t) throws Exception {
                consumer.accept(t);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(@NonNull Throwable throwable) throws Exception {
                //do something for error
            }
        }));
    }

    /**
     * 订阅事件,与Rxjava一致,自动添加事件到CompositeDisposable中,方便回收
     * 可自己处理异常
     */
    public void subscribe(final Consumer<T> consumer, final Consumer<? super Throwable> error) {
        mDisposable.add(mFlowable.subscribe(new Consumer<T>() {
            @Override
            public void accept(@NonNull T t) throws Exception {
                consumer.accept(t);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(@NonNull Throwable throwable) throws Exception {
                error.accept(throwable);
            }
        }));
    }
}

然后开始使用!!!
为了体现出效果,用RecyclerView创建出一个列表,当点击item时发送item的position
在Activity中订阅Integer类型的事件,接收到事件后把position 吐司出来

adapter代码

@BaseScope
public class MainRvAdapter extends RecyclerView.Adapter<MainRvAdapter.MyViewHolder> {
    @Inject
    SubstribeManager mManager;
    //将Adapter也注入
    @Inject
    public MainRvAdapter() {
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        TextView tv = new TextView(parent.getContext());
        return new MyViewHolder(tv);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        TextView tv = (TextView) holder.itemView;
        tv.setText("随意的布局,随意的数据"+position);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //发送事件,类型为Integer的position
                mManager.post(position);
            }
        });
    }

    @Override
    public int getItemCount() {
        return 100;
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        public MyViewHolder(View itemView) {
            super(itemView);
        }
    }
}

再来看看BaseActivity代码

@BaseScope
public abstract class BaseActivity extends AppCompatActivity{
    //CompositeDisposable在BaseModule中被提供,并且作用域是BaseScope,所以在该域内该对象都只有一份
    @Inject
    CompositeDisposable mDisposable;
    @Inject
    SubstribeManager mManager;
    private BaseComponent mBaseComponent;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过父组件AppComponent来注册BaseComponent
        mBaseComponent = ((App) getApplication()).getAppComponent().baseComponent(new BaseModule(this));
        inject();
    }

    /**
     * 得到BaseComponent
     * @return
     */
    protected BaseComponent getBaseComponent() {
        return mBaseComponent;
    }

    /**
     * 需要子类进行inject
     */
    protected abstract void  inject();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //回收CompositeDisposable,因整个BaseScope域都用的同一个,所以能把所有产生的Disposable回收
        mDisposable.dispose();
    }
}

主要的Activity代码

public class MainActivity extends BaseActivity {
    @Inject
    MainRvAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
        rv.setLayoutManager(new LinearLayoutManager(this));
        rv.setAdapter(mAdapter);
        Log.d("MainActivity", mDisposable+"");
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //这里可以尝试提前dispose(),就会发现之前订阅的事件已经接收不到了,也验证了事件被自动添加到CompositeDisposable中了
//              mDisposable.dispose();
                startActivity(new Intent(MainActivity.this,Main2Activity.class));
            }
        });
        subtribeData();
    }

    @Override
    protected void inject() {
        getBaseComponent().inject(this);
    }

    /**
     * 订阅Integer类型事件
     */
    private void subtribeData() {
        //这里不需要再考虑回收问题
        mManager.subscribeResult(Integer.class).subscribe(new Consumer<Integer>() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                Toast.makeText(MainActivity.this, "被点到的item为: "+integer, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

结果图


Screenshot_2017-05-24-21-25-17.png

这里验证一下两个Activity中维持的事件管理器(SubstribeManager)是否是同一个

image.png

看截图就会发现不同的Activity之间提供的处于BaseScope域中的对象不相同,所以Activity之间相互独立,发送的事件也不会"窜门"!

最后需要验证的就是Disposable是否会被自动回收

QQ截图20170526121332.png

在点击事件中提前把CompositeDisposable集合回收了,
就会发现再也收不到之前订阅的事件了,这也就验证了Disposable被自动添加到集合中了,并在onDestroy()时,通过父类的实现,而被回收了


本篇文章是关于Rxbus组件内(局部)通信的简单使用,之后的文章会带来更加有意思的用法!

**
github:https://github.com/lightofrain/LocalRxbus.git
**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,499评论 25 707
  • RxBus、EventBus因为解耦太彻底,滥用的话,项目可维护性会越来越低;一些简单场景更推荐用回调、Subje...
    YoKey阅读 15,644评论 32 81
  • 怎么办,我好想他 即使他告诉我他喜欢的人不是我 但是我还是喜欢他
    天然美阅读 137评论 0 0
  • 假如我是断臂残躯,能否有勇气活下去。看到那么多励志的故事,也只是心中一暖,感叹而过。 尼克·武伊契奇(Nick V...
    爱上一叶浮萍阅读 171评论 0 1
  • 如果没记错的话,我小时刚有电视的时候,突然就有了一个ABC英语培训,几乎每家的小朋友都有一套电视英语讲座的教材。当...
    万能的老J阅读 512评论 6 5