观察者模式,其实对于Android开发者而言,并不陌生,button的setOnClickListener,就是一个典型的观察者模式。控件button是被观察者,它产生一个事件(点击),观察者OnClickListener接收到,做出相应的处理,而setOnClickListener就是订阅者,它将两者连接起来
以上面为例,观察者模式需要具备的三个角色:被观察者,观察者,事件,一个动作:订阅
角色 | 作用 | 类别 |
---|---|---|
被观察者(Observable) | 产生事件 | 控件 |
观察者(Observer) | 接收事件,并给出响应动作 | OnClickListener |
订阅(Subscribe) | 连接 被观察者 & 观察者 | setOnClickListener |
事件(Event) | 被观察者 & 观察者 沟通的载体 | 控件被点击 |
在第一次接触这些词的时候,我满脑子的问号,看官方文档也是云山雾里的。后来我翻看了各种文档,说一下我对这些词的理解:
响应式编程(Reactive Programming RP):万物皆可视为数据流,比如:用户的操作,网络数据,某个代码状态变更
函数式编程(Functional Programming FP):将逻辑抽象为旧数据如何映射为新数据。(比如rx或者kotlin里的各种操作符)
函数响应式编程(Reactive Functional Programming RFP): 将数据流的各种操作(创建,结合,过滤等)抽象为函数(操作符),使得数据流可以自由的组合这些函数实现各种数据流的映射
推荐文章:
那些年我们错过的响应式编程
什么是函数式编程思维
ReactiveX是Reactive Extensions的缩写,一般简写为Rx,Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流。Rx的大部分语言库由ReactiveX这个组织负责维护,比较流行的有RxJava/RxJS/Rx.NET,社区网站是 reactivex.io。
ReactiveX.io给的定义是,Rx是基于观察者模式的实现了异步编程接口,ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。Rxjava则是Reactive Extensions的java实现
简单说,Rxjava是函数响应式编程思想的一种体现,基于观察者模式实现的一种异步编程接口。
原理是: 被观察者 (Observable) 通过 订阅(Subscribe) 按顺序发送数据 给观察者 (Observer), 观察者(Observer) 按顺序接收事件 & 作出对应的响应动作。
特点:
操作符丰富
支持背压
支持线程切换操作
学习成本和上手难度较高
Flow是kotlin提供的一个工具( kotlinx 包下的组件),它也是函数响应式编程(RFP)思想的一种体现,也是使用的观察者模式,但拥有尽可能简单的设计, 对 Kotlin 以及协程友好且遵从结构化并发。
特点:
支持线程(协程)
跟协程绑定的比较多
支持背压
操作符和rx整体来说差不多
学习成本低(前提是得会协程)
LiveData 是 androidx 包下的组件,是 Android 生态中一个的简单的生命周期感知型容器。与常规的可观察类不同,LiveData 具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。
这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
特点:
可以感知生命周期
只能在主线程更新数据
不支持背压
不支持防抖
没有操作符
关于更多LiveData的使用和源码,可以看我之前的文章: JetPack框架组件2——liveData的使用和源码分析
同button的setOnClickListener一样,RxJava和kotlin flow 都是基于观察者模型实现的编程接口。所以和button的setOnClickListener使用类似,即:
实现被观察者(button)
实现观察者(OnClickListener)
注册(setOnClickListener)
被观察者发出事件,观察者接受到(点击)
实现被观察者observable
// 1. 创建被观察者 Observable 对象
val observable = Observable.create( object : ObservableOnSubscribe<String> {
// create() 是 RxJava 最基本的创造事件序列的方法
//2.在复写的subscribe()里定义需要发送的事件
override fun subscribe(emitter: ObservableEmitter<String>) {
// 通过 ObservableEmitter类对象产生事件并通知观察者
// ObservableEmitter类介绍
// a. 定义:事件发射器
// b. 作用:定义需要发送的事件 & 向观察者发送事件
emitter.onNext("hello rxjava")//发送事件
emitter.onComplete()//发送完成事件
}
})
实现观察者observer
// 1. 创建观察者 (Observer )对象
val observer = object : Observer<String> {
// 2. 创建对象时通过对应复写对应事件方法 从而 响应对应事件
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "开始采用subscribe连接")
}
override fun onNext(t: String) {
Log.d(TAG, "对Next事件作出响应: $t")
}
override fun onError(e: Throwable) {
Log.d(TAG, "对Error事件作出响应")
}
override fun onComplete() {
Log.d(TAG, "对Complete事件作出响应")
}
}
注册
observable.subscribe(observer)
执行结果
原理
这一块源码的实现,感兴趣可以看我之前的博客:Android之Rxjava2.X 9————Rxjava源码阅读1
其实Rxjava上面部分源码的实现很简单,一句话概括
使用Observable.create创建时,需要传入的ObservableOnSubscribe接口中的subscribe方法参数,这个参数是用来发射数据.它其实是Observer的包装类
源码核心逻辑
public final class ObservableCreate<T> extends Observable<T> {
@Override
protected void subscribeActual(Observer<? super T> observer) {
CreateEmitter<T> parent = new CreateEmitter<T>(observer);
observer.onSubscribe(parent);
try {
//source即为ObservableOnSubscribe
source.subscribe(parent);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
parent.onError(ex);
}
}
}
通过源码也可以看出来,当完成observable.subscribe(observer)时,observable才可以发送数据
更简洁的写法
Observable
.just("hello rxjava") //构造简单的Observable方法
.subscribe { Log.d(TAG, "$it") } //可以只实现onNext方法
//日志:hello rxjava
实现被观察者flow
val flow: Flow<String> = flow { // 流构建器
emit("hello flow") // 发送下一个值
}
实现观察者flowCollector
val flowCollector: FlowCollector<String> = FlowCollector {
Log.d(TAG, "$it")
}
注册
注意,flow是存在suspend标识,所以必须运行在协程中
GlobalScope.launch{
flow.collect(flowCollector)
}
执行结果
原理
看了flow的原理,其实和rxjava类似。flow创建,传入的FlowCollector接口,其实也是flowCollector进行包装后的对象
public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {
public final override suspend fun collect(collector: FlowCollector<T>) {
val safeCollector = SafeCollector(collector, coroutineContext)
try {
collectSafely(safeCollector)
} finally {
safeCollector.releaseIntercepted()
}
}
}
一般写法
flow {
emit("hello flow") // 发送下一个值
}.collect{
Log.d(TAG, "$it")
}
//日志: MainActivity: hello flow
是否可以类似Rxjava那也,也可以发射开始,发射结束,error等状态的回调。这个是可以的
onStart:数据流开始发射
onEach:数据流中的每一个数据发射时的回调
onCompletion:数据流结束发射
onEmpty:当数据流中没有发射任何数据时
catch: 发生异常
flow {
emit(1)
emit(2)
emit(3)
emit(4)
}.catch { e ->
// 发生了异常。显示异常信息
Log.d(TAG, "加载错误: $e")
}.onEmpty {
// 空白数据
Log.d(TAG,"什么数据都没有")
}.onStart {
Log.d(TAG,"正在加载中")
}.onEach {
Log.d(TAG,"开始处理 $it")
}.onCompletion { e->
if(e == null){
Log.d(TAG,"加载结束 $it")
}else{
Log.d(TAG,"加载失败 $e")
}
}.collect {
Log.d(TAG,"加载成功 $it")
println(it)
}
Rxjava的线程操作功能符号有两个:
subscribeOn() 指定被观察者的线程,有一点需要注意就是如果多次调用此方法,只有第一次有效。
observerOn() 指定观察者的线程,每指定一次就会生效一次。
subscribeOn
Observable
.create<Int> {
Log.e(TAG, "threadName:" + Thread.currentThread().getName());
it.onNext(1);
}.subscribeOn(Schedulers.newThread())
.subscribe { Log.d(TAG, "$it") }
observerOn()
Observable.fromIterable(listOf(0, 1, 2, 3, 4)) //根据list快速创建一组Observable
.map { //转化操作符
Log.d(TAG, "subscribeOn threadName: ${Thread.currentThread().name} it = $it" )
return@map it * 10
}.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe {
Log.d(TAG, "observerOn threadName: ${Thread.currentThread().name}")
Log.d(TAG, "onNext: $it")
}
flow的线程操作符只有flowOn
flowOn介绍:
操作符对上游范围有效, 范围是指两个flowOn之间, 如果只有一个flowOn,则上游全部有效
最后一个flowOn后的操作所在线程与当前整个flow所在的线程池相同
(0..4).asFlow()
.onStart { Log.d(TAG, "flow start threadName: ${Thread.currentThread().name}" )}
.flowOn(Dispatchers.IO)
.map {
// 运行在 dispatcher2
Log.d(TAG, "map threadName: ${Thread.currentThread().name} it = $it" )
it * 10
}
.flowOn(Dispatchers.Main)
.collect {
// 运行的协程取决于整个 flow 在哪个协程调用
Log.d(TAG, "collect ${Thread.currentThread().name} it = $it" )
println(it)
}
针对的场景:
Observable.create(ObservableOnSubscribe<Int> {
var i = 0
while (true) {
i++
it.onNext(i)
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.subscribe {
Thread.sleep(5000);
Log.d(TAG, "onNext: $it")
}
在异步订阅的(比如网络请求),被观察者发生事件的速度太快,观察者来不及接受所有的事件,从而缓存区中的事件越积越多,最终导致缓存区溢出,事件丢失并OOM
定义
Backpressure,也称为Reactive Pull,就是下游需要多少(具体是通过下游的request请求指定需要多少),上游就发送多少。
作用
在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略
背压策略的原理
对于观察者:响应式拉取,即观察者根据自己的实际需求接受事件
对于被观察者:反馈控制,即被观察者根据观察者的接受能力,从而控制发送事件的速度
对于缓存区:对超出缓存区大小的事件进行丢弃,保留,报错。
在Rxjava中,如果需要使用背压,需要使用Observable(被观察者)的另一种实现:Flowable。
Flowable的特点:
对应的观察者变为Subscribe
所有操作符强制支持背压
默认的缓存区的大小为:128。缓存区使用队列存放事件
Flowable的能力
request()可以控制observer(观察者)接受事件的速度
requested()可以控制Observable(被观察者)发送事件的速度
背压策略:处理缓存区的逻辑
下面以异步订阅为例,讲解这三个能力的使用,更详细的使用以及同步订阅的处理,参考我的博文
Android之Rxjava2.X 8————Rxjava 背压策略
request()
@SuppressLint("CheckResult")
fun backpressure(){
Flowable.create(FlowableOnSubscribe<Int> {
Log.d(TAG, "发送事件 1");
it.onNext(1);
Log.d(TAG, "发送事件 2");
it.onNext(2);
Log.d(TAG, "发送事件 3");
it.onNext(3);
Log.d(TAG, "发送事件 4");
it.onNext(4);
Log.d(TAG, "发送完成");
it.onComplete();
},BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Subscriber<Int> {
override fun onSubscribe(s: Subscription?) {
s?.request(3)
}
override fun onNext(t: Int?) {
Log.d(TAG, "接收到了事件 $t");
}
override fun onError(t: Throwable?) {
Log.d(TAG, "onError $t");
}
override fun onComplete() {
Log.d(TAG, "onComplete ");
}
})
注意点:
如果未设置request,则默认未接收
观察者不接收事件的情况下,被观察者继续发送事件 & 存放到缓存区,再按需求取出
超出缓存区的数据,会按照策略进行处理
requested()
异步订阅下,**requested()**反向控制Observable
Flowable.create(
FlowableOnSubscribe<Int> {
// 被观察者一共需要发送500个事件
for (i in 0..500) {
var flag = false;
// 若requested() == 0则不发送
while (it.requested() == 0L) {
if (!flag) {
Log.d(TAG, "不再发送");
flag = true;
}
}
// requested() ≠ 0 才发送
Log.d(TAG, "发送了事件" + i + ",观察者可接收事件数量 = " + it.requested());
it.onNext(i);
}
},
BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Subscriber<Int> {
override fun onSubscribe(s: Subscription?) {
subscription = s
}
override fun onNext(t: Int?) {
Log.d(TAG, "接收到了事件 $t");
}
override fun onError(t: Throwable?) {
Log.d(TAG, "onError $t");
}
override fun onComplete() {
Log.d(TAG, "onComplete ");
}
})
背压策略
目前rxjava有5种策略
BackpressureStrategy.ERROR 超出缓存区,抛出异常
BackpressureStrategy.MISSING 提示缓存区满了
BackpressureStrategy.BUFFER 缓存区大小设置成无限大
BackpressureStrategy.DROP 超过缓存区大小(128)的事件丢弃
BackpressureStrategy.LATEST 只保存最新(最后)事件,超过缓存区大小(128)的事件丢弃(即如果发送了150个事件,缓存区里会保存129个事件(第1-第128 + 第150事件))
flow的操作符很简单,使用一个操作符解决:
buffer(capity, onBufferOver)
capity 缓存的大小
onBufferOverflow 缓存超出的策略
同时给予buffer衍生了一系列背压相关的操作符
conflate(): 只取最新的数据,等价 buffer(0, DROP_OLDEST),即不缓存数据,直接取最新数据的处理
collectLatest():类似conflate,但是不会直接用新数据覆盖老数据,而是每一个都会被处理,只不过如果前一个还没被处理完后一个就来了的话,处理前一个数据的逻辑就会被取消。
mapLatest:同理 collectLatest
flatMapLatest:同理 collectLatest
flow {
(1..500).forEach {
delay(100)
println("emit: $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
emit(it)
}
}
.buffer(capacity = 128, onBufferOverflow = BufferOverflow.DROP_OLDEST)
.collect {
delay(500)
Log.d(TAG, "接收到了事件 $it");
}
采用了DROP_OLDEST策略,此时当缓冲区超出处理速度时,就会丢弃老数据
冷流:即下游无消费行为时,上游不会产生数据,只有下游开始消费,上游才从开始产生数据。
热流:即无论下游是否有消费行为,上游都会自己产生数据
一般的flow,仅有一个观察者。冷流
//构建
val testFlow = flow<String>{
emit("hello")
emit("flow")
}
//接收
coroutineScope.launch{
testFlow.collect{ value->
println(value)
}
}
//打印
hello
flow
有状态的FLow 可以有多个观察者,热流
需要时传入初始值, initialState
常用作与UI相关的数据观察,类比LiveData
//创建
val uiState=MutableStateFlow(Result.Loading)
//监听
coroutineScope.launch{
uiState.collect{ value->
println(value)
}
}
//赋值
uiState.value=Result.Sucess
//打印结果
Result.Loading
Result.Sucess
有三个参数值
replay - 重播给新订阅者的值的数量(不能为负,默认为零)
extraBufferCapacity - 除了replay之外缓冲的值的数量。 当有剩余缓冲区空间时, emit不会挂起(可选,不能为负,默认为零)
onBufferOverflow - 配置缓冲区溢出的操作(可选,默认为暂停尝试发出值)
//创建
val signEvent=MutableSharedFlow <String> ()
//监听
coroutineScope.launch{
signEvent.collect{ value->
println(value)
}
}
//赋值
signEvent.tryEmit("hello")
signEvent.tryEmit("shared flow")
//打印结果
hello
shared flo
Rxjava最大的优势就是种类繁多的操作符。常用的具体如下,这里只进行列举功能,详细的可以参考后面的文档:
创建操作 Android之Rxjava2.X 2————Rxjava 创建操作符
变换操作 Android之Rxjava2.X 3————Rxjava 变换操作符
组合操作 Android之Rxjava2.X 4————Rxjava 组合操作符
过滤操作 Android之Rxjava2.X 5————Rxjava 过滤操作符
功能 Android之Rxjava2.X 6————Rxjava 功能操作符
条件操作符:Android之Rxjava2.X 7————Rxjava 条件操作符
我发现flow的操作符,基本上可以替代RxJava,也提供了诸多操作符来处理数据
具体使用和示例参考博客:https://juejin.cn/post/6989536876096913439
创建flow
末端操作,在flow最后调用,此时返回的不是一个flow了
变换操作
组合操作符
过滤操作符
功能操作符
模拟登陆场景描述:
两个EditText,输入账号和密码。一个Button,点击发起登录
只有两个EditText有输入,此时button从灰色变成蓝色,并且可以点击
button点击要具有防抖功能(1s内只有第一次算有效点击)
需求分析
联合判断: 只有当账号和密码都有输入时,才可以点击
将账号和密码的输入视为两个数据流,当两个数据流都符合要求时,产生一个新的数据流表示是否可点
可以考虑使用组合操作符:combineLatest(combine)。将两个EditText的产生的字符流按照时间线,合并为一个表示是否可以点击的数据流
button的防抖功能
将button的点击视为数据流规定时间周期之内,只接受第一个事件
可以考虑过滤操作符: throttleFirst(sample)在1s内只接受第一个事件,或者直接使用sample(debounce)防抖操作符
同时,这里引入了一个新的问题,如何将Android UI操作转化为数据流
如果将View的操作转化为Observable,可以直接使用现有的第三方库(但tt好像没有引入)
compile ‘com.jakewharton.rxbinding:rxbinding:0.4.0’
自己实现一个View操作转化为Observable,参考ObservableCreate
将button点击转化为Observable(其他逻辑的需要转化成Observable可以参考)
class ViewClickObservale(private val view: View) : Observable<View>() {
override fun subscribeActual(observer: Observer<in View>) {
val listener = Listener(view, observer)
observer.onSubscribe(listener)
view.setOnClickListener(listener)
}
internal class Listener(private val view: View, private val observer: Observer<in View>) :
MainThreadDisposable(), View.OnClickListener {
override fun onClick(v: View) {
if (!isDisposed) {
observer.onNext(v)
}
}
override fun onDispose() {
view.setOnClickListener(null)
}
}
}
完成需求:
@SuppressLint("CheckResult")
private fun logOnRxjava() {
val account: EditText = findViewById (R.id.account)
val password: EditText = findViewById (R.id.password)
val logOn: Button = findViewById (R.id.log_on)
logOn.isEnabled = false
//使用RxBinding将EditText将textChanges转化为Observable
//skip跳过 一开始EditText无任何输入时的空值
val accountObservable = RxTextView.textChanges(account).skip(1)
val passwordObservable = RxTextView.textChanges(password).skip(1)
//使用combineLatest将事件合并 两个EditText的输入-->button是否可点
Observable.combineLatest(accountObservable,passwordObservable) { _, _ ->
account.text.isNotEmpty() && password.text.isNotEmpty()
}.subscribe{
logOn.isEnabled = it
}
//使用自定义的Observale,将view的点击转化为Observale
ViewClickObservale(logOn)
.throttleFirst(1, TimeUnit.SECONDS)//根据时间,进行防抖
.subscribe{
Toast.makeText(this,"正在登录...",Toast.LENGTH_LONG).show()
}
}
实现效果:
此处应该有视频,但掘金不支持视频
将ui流数据转化为flow,因为ui在主线程,而flow的collect必须在协程中调用,所以使用channelFlow创建flow。更多可以参考文章:使用更为安全的方式收集 Android UI 数据流
private fun viewClickFlow(view: View): Flow<View?> = channelFlow {
view.setOnClickListener {
trySend(view)
}
}
private fun viewTextChangeFlow(view: TextView):Flow<CharSequence?> = channelFlow {
val callback = object : TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
trySend(p0)
}
override fun afterTextChanged(p0: Editable?) {}
}
view.addTextChangedListener(callback)
awaitClose {
view.removeTextChangedListener(callback)
}
}
需求实现:
private fun logOnFLow(){
val account: EditText = findViewById(R.id.account)
val password: EditText = findViewById(R.id.password)
val logOn: Button = findViewById(R.id.log_on)
logOn.isEnabled = false
// 将EditText的文本变化转化为flow流
val accountFlow = viewTextChangeFlow(account)
val passwordFlow = viewTextChangeFlow(password)
// 开启协程
lifecycleScope.launchWhenStarted {
// 使用combine将事件合并 两个EditText的输入-->button是否可点
accountFlow.combine(passwordFlow) { _, _ ->
account.text.isNotEmpty() && password.text.isNotEmpty()
}.collect {
logOn.isEnabled = it
}
viewClickFlow(logOn)
.sample(1000L)
.collect {
Log.e(TAG,"正在登录...")
}
}
}
模拟联合请求场景
有一段文本的数据来源于两个接口数据的混合处理,并且要分别进行本地缓存
数据优先从本地获取,如果获取不到,在进行网络请求,请求完成后,在进行本地缓存
两个接口的数据要同时展示
需求分析
多级缓存,请求时先判断本地缓存是否有数据,如果没有数据,则进行网络请求
将本地缓存的数据和网络请求的数据视为数据流,并按顺序发送数据流。并将第一个有效的数据返回
使用组合操作符concat(merge) 将本地缓存数据流,网络请求的数据流合并为一个流,并串行依次执行
使用过滤操作符take(1)获取合并流的第一个有效元素
联合请求:将多个网络请求的数据源,同时进行展示
将两个数据内容视为数据流,并一对一进行合并为一个新的数据流
使用组合操作符zip,可以满足需求
将网络请求和本地获取数据包装为Observable
object DataControl {
const val BAI_DU = "https://www.baidu.com/"
const val SOU_GOU = "https://www.sogou.com/"
private const val SP_NAME = "sp_name"
private val retrofit: RetrofitApi by lazy {
Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.client(OkHttpClient())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BAI_DU)
.build()
.create(RetrofitApi::class.java)
}
//使用retrofit 可以直接转化为Observable
fun getNetworkBaiduString() = retrofit.baidu(BAI_DU)
fun getNetworkSougouString() = retrofit.souGou(SOU_GOU)
//获取本地数据,并转化为Observable
fun getLocalString(cxt: Context, channel: String): Observable<String?> {
val baiduString = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
.getString(channel, "")
//如果本地没有数据,直接使用empty通知完成
return if (baiduString.isNullOrEmpty()) Observable.empty() else Observable.just(baiduString)
}
//将数据保存到本地
fun setLocalString(cxt: Context, channel: String, data: String) {
val editor = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE).edit()
editor.putString(channel, data)
editor.apply()
}
interface RetrofitApi {
@GET
fun baidu(@Url url: String?): Observable<String?>
@GET
fun souGou(@Url url: String?): Observable<String?>
}
}
多级缓存的实现
private fun getBaiduData():Observable<String?> {
val baiduLocalObservable = DataControl
.getLocalString(this, DataControl.BAI_DU)
.map {
//进行额外处理
"数据来源:本地 百度->$it"
}
val baiduNetworkObservable = DataControl
.getNetworkBaiduString()
.subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
.map {
//保存数据
DataControl.setLocalString(this, DataControl.BAI_DU, it)
//进行额外处理
"数据来源:网络 百度->$it"
}
//获取百度数据
//concat 串行执行本地。网络的两个数据流
//只返回第一个数据
return Observable.concat(baiduLocalObservable, baiduNetworkObservable)
.take(1)
}
private fun getSouGouData():Observable<String?> {
val souGouLocalObservable = DataControl
.getLocalString(this, DataControl.SOU_GOU)
.map {
"数据来源:本地 搜狗->$it"
}
val souGouNetworkObservable = DataControl
.getNetworkSougouString()
.subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
.map {
DataControl.setLocalString(this, DataControl.SOU_GOU, it)
"数据来源:网络 搜狗->$it"
}
//获取百度数据
//concat 串行执行本地。网络的两个数据流
//只返回第一个数据
return Observable.concat(souGouLocalObservable, souGouNetworkObservable)
.take(1)
}
联合请求的实现
val baidu: TextView = findViewById(R.id.baidu)
val souGou: TextView = findViewById(R.id.souGou)
val button :Button = findViewById(R.id.button)
button.setOnClickListener {
//zip,将百度和搜狗的数据一一对应进行组合。然后发出去
Observable.zip(getBaiduData(),getSouGouData()) { s1, s2 ->
Pair(s1, s2)
}.subscribe {
baidu.text = "时间: ${System.currentTimeMillis()} ${it.first}"
souGou.text ="时间: ${System.currentTimeMillis()} ${it.second}"
}
}
实现效果:
此处应该有视频,但掘金不支持视频
将网络请求和本地获取数据包装为flow
object DataFlowControl {
const val BAI_DU = "https://www.baidu.com/"
const val SOU_GOU = "https://www.sogou.com/"
private const val SP_NAME = "sp_name"
private val retrofit: RetrofitApi by lazy {
Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.client(OkHttpClient())
.addCallAdapterFactory(FlowCallAdapterFactory.create())
.baseUrl(BAI_DU)
.build()
.create(RetrofitApi::class.java)
}
//使用retrofit 可以直接转化为Observable
fun getNetworkBaiduString() = retrofit.baidu(BAI_DU)
fun getNetworkSouGouString() = retrofit.souGou(SOU_GOU)
//获取本地数据,并转化为Observable
fun getLocalString(cxt: Context, channel: String): Flow<String?> {
val baiduString = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
.getString(channel, "")
//如果本地没有数据,直接使用empty通知完成
return if (baiduString.isNullOrEmpty()) emptyFlow() else flowOf(baiduString)
}
//将数据保存到本地
fun setLocalString(cxt: Context, channel: String, data: String) {
val editor = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE).edit()
editor.putString(channel, data)
editor.apply()
}
interface RetrofitApi {
@GET
fun baidu(@Url url: String?): Flow<String?>
@GET
fun souGou(@Url url: String?): Flow<String?>
}
}
多级缓存的实现
private fun getBaiduFlowData():Flow<String?>{
val baiduLocalFlow = DataFlowControl
.getLocalString(this, DataControl.BAI_DU)
.map {
//进行额外处理
"数据来源:本地 百度->$it"
}
val baiduNetWorkFlow = DataFlowControl
.getNetworkBaiduString()
.flowOn(Dispatchers.IO)
.map {
//保存数据
it?.let { DataControl.setLocalString(this, DataControl.BAI_DU, it) }
//进行额外处理
"数据来源:网络 百度->$it"
}.flowOn(Dispatchers.Main)
//获取百度数据
//merge 串行执行本地。网络的两个数据流
//take(1)只返回第一个数据
return listOf(baiduLocalFlow, baiduNetWorkFlow)
.merge()
.take(1)
}
private fun getSouGouFlowData():Flow<String?>{
val souGouLocalFlow = DataFlowControl
.getLocalString(this, DataControl.SOU_GOU)
.map {
//进行额外处理
"数据来源:本地 搜狗->$it"
}
val souGouNetWorkFlow = DataFlowControl
.getNetworkSouGouString()
.flowOn(Dispatchers.IO)
.map {
//保存数据
it?.let {DataControl.setLocalString(this, DataControl.SOU_GOU, it)}
//进行额外处理
"数据来源:网络 搜狗->$it"
}.flowOn(Dispatchers.Main)
//获取搜狗数据
//merge 串行执行本地。网络的两个数据流
//take(1)只返回第一个数据
return listOf(souGouLocalFlow, souGouNetWorkFlow)
.merge()
.take(1)
}
联合请求的实现
private fun requestFlow() {
val baidu: TextView = findViewById(R.id.baidu)
val souGou: TextView = findViewById(R.id.souGou)
val button: Button = findViewById(R.id.button)
button.setOnClickListener {
lifecycleScope.launch {
//zip,将百度和搜狗的数据一一对应进行组合。然后发出去
getBaiduFlowData().zip(getSouGouFlowData()) { s1, s2 ->
Pair(s1, s2)
}.collect {
baidu.text = "时间: ${System.currentTimeMillis()} ${it.first}"
souGou.text = "时间: ${System.currentTimeMillis()} ${it.second}"
}
}
}
}
实现效果如上
Kotlin Flow 介绍
异步流 kotlin语言中文站
对比 RxJava 入门 Kotlin-flow - 掘金
【Kotlin Flow】 一眼看全——Flow操作符大全 - 掘金
Carson带你学Android:这是一篇清晰易懂的Rxjava入门教程