前面一节大致讲了讲,我们的成品到底是怎么样的?那么这一节,我们就来讲讲如何初步地隐藏自己Disposable的存储和销毁。
首先说说最常见的方式,在Activity中定义和初始化一个CompositeDisposable,然后在生成需要管理的Disposable后,将其放进这个管理类中,然后在onDestory中直接通过CompositeDisposable对其内管理的Disposable进行统一清除,如下(有些时候实在是不太喜欢专门写博客,就是因为还要专门写一个demo):
abstract class BaseDisposableActivity(override var layout: Int?) : BaseActivity(layout) {
constructor() : this(null)
var mCompositeDisposable: CompositeDisposable? = null
fun addDisposable(disposable: Disposable) {
if (mCompositeDisposable == null)
mCompositeDisposable = CompositeDisposable()
mCompositeDisposable?.add(disposable)
}
override fun onDestroy() {
mCompositeDisposable?.dispose()
super.onDestroy()
}
}
我们可以看见,如果我们需要在某个Activity进行对Disposable的统一管理,那么我们就必须在activity中定义一个CompositeDisposable用于对Disposable的管理。这部分的逻辑相信大家一定都非常容易理解。
OK,我们整理一下逻辑,我们在使用的时候首先需要定义一个Disposable,然后通过addDisposable方法将其放入CompositeDisposable中,最后在activity销毁的时候,将Disposable回收。这就是我们的基本逻辑,看起来完全没有问题,毕竟java本身就是命令式的语言,函数并不是其第一公民。那么我们有没有办法将对于Disposable管理的过程也进行隐藏呢?毕竟Activity作为View层,我真的不希望其涉及过多的非业务逻辑,就像我们之前所说的,我们能不能在Activity中仅仅关心那三个最最基本的问题,而将其余丰富的,与业务无关的实现过程隐藏呢?
以前看过类似的说法,渗透测试人员,往往不会将自己的技术原理,实现细节过多地暴露出来,因为很多人都有这个毛病,当别人非常简洁概要地将一个苦难的技术点解释的非常清楚的时候,他们不会感慨别人的表达能力以及知识水平的强大,反而会认为自己的理解能力非常的强,于此同时,还会感觉这个工作太简单,所谓的“我上我也行”。
这里我定义了一种特殊的操作,并将其取名为Onion,中文翻译就是洋葱,灵感来源于那首著名的歌曲。一方面,我希望通过这样的操作对于代码本身进行抽丝剥茧般的处理,将原本方法的层层嵌套关系通过函数式的方式碾平为链式的调用,甚至加上一些惰性的执行逻辑(我也是我非常喜欢Kotlin的原因,因为Java做到这些其实虽然可以,但是代码却比kotlin要冗余和复杂的多,我一直感觉Java的优势在于降低培训的成本,降低开发的门槛,从而让更多的人能够参与到开发过程中),于此同时,另外一方面,最初我想到这个操作的时候我恰恰为人所伤,我希望像歌词所言,如果你能一层一层一层一层地剥开我的心,你会发现,你是我最压抑,最深处的秘密。可惜,过去已经不能够再次回来,我也不是过去的我了。
解决问题的方式不过是尽可能挖掘当前的已知信息罢了,那么我们现在所已知的信息有哪些呢?
如果没有封装的话,形式就是mCompositeDisposable.add(RxView.clicks(View))。
我工作之后只在一家公司呆过,所以并不知道别的公司的代码是怎么样的?不过估计应该也差不多吧。
你可以看到,前面的代码基本没有可以下手的空间,而且,还是老问题,暴露了太多的技术细节,View层,我操作那么多的细节是不是有毒?所以我们首先的目的就是通过kotlin原生所支持的拓展方法将RxView的相关细节隐藏。代码如下:我们可以看到,我在拓展方法中将视图与RxView相绑定,并且极其人性化地添加了防抖的操作。
@Deprecated("disposable may cause memory leak")
private fun View.flowableClickUnsafe(consumer: Consumer<Any>) = flowableClick(this).subscribe(consumer)
fun <T : View> flowableClick(view: T): Flowable<Any> = RxView.clicks(view)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.debounce(500, TimeUnit.MILLISECONDS)
.toFlowable(BackpressureStrategy.DROP)
这样一来,RxView的实现细节其实就已经被隐藏了。我们的目的之一就已经实现了。
现在我们的代码就能转换成这样了,RxView的细节被隐藏了。
helloWorld.flowableClickUnsafe(Consumer {
//Todo: do on click helloworld
})
//原来是这样的
RxView.clicks(helloWorld).subscribe {
//Todo:do on click helloworld
}
而且因为Kt对于函数式的支持,我们添加一个函数转Consumer的拓展方法
fun <T> ((T) -> Unit).toConsumer() = Consumer<T> { this.invoke(it) }
private fun View.flowableClickUnsafe(consumer: Any->Unit) = flowableClick(this).subscribe(consumer.toConsumer())
这样一来,代码就可以进一步省略,是不是很棒,所有细节都被隐藏了!
helloWorld.flowableClickUnsafe{
//Todo: do on click helloworld
}
尽管前面我们已经成功隐藏了,RxView的操作细节,这样做绝不仅仅是为了代码的好看,虽然,好看的确非常重要,但是这样进行操作,我们可以很方便地对所有的点击事件进行管理,只需要在拓展方法里面进行管理就行了,有点代理模式那个味道了。
好了,终于到了重头戏了,如何隐藏disposable细节?
我们通过拓展方法来隐藏RxView的另一个目的就是为了这个,人为打桩,创造一个能让我动手脚的地方。
核心想法就是基于函数式思想,将addDisposable()方法延后到Flowable调用subscribe后进行操作。
首先我们定义一个函数用于处理disposable,如下:from方法用于引入另外一个参量,从而将Activity:F转换为(Disposable:T)->Unit,这样一来,我们只需要关心如何生成Disposable就行了。
fun funAddDisposable(): (Disposable) -> Unit = from { addDisposable(it.second) }
fun <F, T> F.from(pairConsumer: (Pair<F, T>) -> Unit): (T) -> Unit = {
pairConsumer.invoke(Pair(this, it))
}
当前已经得到了函数(Disposable)->Unit,同理,我希望将其与Flowable和Consumer划上关联,同理,通过拓展方法来实现,看懂了上一个from方法这个函数其实并不苦难,这个函数实现了从(F)->Unit到(T)->§->Unit的转换,毕竟disposable需要Flowable和Consumer两者才能生成。
fun <F, T, P> ((F) -> Unit).from(pair2f: (Pair<T, P>) -> F): (T) -> (P) -> Unit = { t ->
{ p ->
this.invoke(pair2f.invoke(Pair(t, p)))
}
}
这样一来,我们的对于点击调用的拓展方法就进一步转换,如下,我们可以看到,连Disposable的添加细节都已经完成了隐藏。
fun View.flowableClick(consumer: (Any) -> Unit) = this.flowableClick(consumer.toConsumer())
private fun View.flowableClick(consumer: Consumer<Any>) =
funAddDisposable().from<Disposable, View, Consumer<Any>> { flowableClick(it.first).subscribe(it.second) }.invoke(this).invoke(consumer)
来看看我们的努力结果吧!方法完美地隐藏了disposable以及RxView的相关细节,即View和实现层完美解耦!
helloWorld.flowableClick { //Todo: do on click
}