当前位置: 首页 > news >正文

网站开发的发展趋势wordpress 第三方登录插件

网站开发的发展趋势,wordpress 第三方登录插件,广州番禺发布,仿百度文库网站源码商业版dedecms(梦织)系统内核LiveData 对于 Java 开发者、初学者或是一些简单场景而言仍是可行的解决方案。而对于一些其他的场景,更好的选择是使用 Kotlin 数据流 (Kotlin Flow)。虽说数据流 (相较 LiveData) 有更陡峭的学习曲线,但由于它是 JetBrains 力挺的 Kotlin 语言的一部分&…

LiveData 对于 Java 开发者、初学者或是一些简单场景而言仍是可行的解决方案。而对于一些其他的场景,更好的选择是使用 Kotlin 数据流 (Kotlin Flow)。虽说数据流 (相较 LiveData) 有更陡峭的学习曲线,但由于它是 JetBrains 力挺的 Kotlin 语言的一部分,且 Jetpack Compose 正式版即将发布,故两者配合更能发挥出 Kotlin 数据流中响应式模型的潜力。

此前一段时间,我们探讨了 如何使用 Kotlin 数据流 来连接您的应用当中除了视图和 View Model 以外的其他部分。而现在我们有了 一种更安全的方式来从 Android 的界面中获得数据流,已经可以创作一份完整的迁移指南了。

在这篇文章中,您将学到如何把数据流暴露给视图、如何收集数据流,以及如何通过调优来适应不同的需求。

数据流: 把简单复杂化,又把复杂变简单

LiveData 就做了一件事并且做得不错: 它在 缓存最新的数据 和感知 Android 中的生命周期的同时将数据暴露了出来。稍后我们会了解到 LiveData 还可以 启动协程 和 创建复杂的数据转换,这可能会需要花点时间。

接下来我们一起比较 LiveData 和 Kotlin 数据流中相对应的写法吧:

#1: 使用可变数据存储器暴露一次性操作的结果

这是一个经典的操作模式,其中您会使用协程的结果来改变状态容器:

△ 将一次性操作的结果暴露给可变的数据容器 (LiveData)

<!--Copyright2020GoogleLLC.SPDX-License-Identifier:Apache-2.0-->classMyViewModel{privateval_myUiState=MutableLiveData<Result<UiState>>(Result.Loading)valmyUiState:LiveData<Result<UiState>>=_myUiState// 从挂起函数和可变状态中加载数据
init{viewModelScope.launch{valresult=..._myUiState.value=result}}}

如果要在 Kotlin 数据流中执行相同的操作,我们需要使用 (可变的) StateFlow (状态容器式可观察数据流):

△ 使用可变数据存储器 (StateFlow) 暴露一次性操作的结果

classMyViewModel{privateval_myUiState=MutableStateFlow<Result<UiState>>(Result.Loading)valmyUiState:StateFlow<Result<UiState>>=_myUiState// 从挂起函数和可变状态中加载数据
init{viewModelScope.launch{valresult=..._myUiState.value=result}}}

StateFlowSharedFlow 的一个比较特殊的变种,而 SharedFlow 又是 Kotlin 数据流当中比较特殊的一种类型。StateFlow 与 LiveData 是最接近的,因为:

它始终是有值的。

它的值是唯一的。

它允许被多个观察者共用 (因此是共享的数据流)。

它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。

当暴露 UI 的状态给视图时,应该使用 StateFlow。这是一种安全和高效的观察者,专门用于容纳 UI 状态。

#2: 把一次性操作的结果暴露出来

这个例子与上面代码片段的效果一致,只是这里暴露协程调用的结果而无需使用可变属性。

如果使用 LiveData,我们需要使用 LiveData 协程构建器:

△ 把一次性操作的结果暴露出来 (LiveData)

classMyViewModel(...):ViewModel(){valresult:LiveData<Result<UiState>>=liveData{emit(Result.Loading)emit(repository.fetchItem())}}

由于状态容器总是有值的,那么我们就可以通过某种 Result 类来把 UI 状态封装起来,比如加载中、成功、错误等状态。

与之对应的数据流方式则需要您多做一点配置:

△ 把一次性操作的结果暴露出来 (StateFlow)

classMyViewModel(...):ViewModel(){valresult:StateFlow<Result<UiState>>=flow{emit(repository.fetchItem())}.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),//由于是一次性操作,也可以使用 Lazily 
initialValue=Result.Loading)}

stateIn 是专门将数据流转换为 StateFlow 的运算符。由于需要通过更复杂的示例才能更好地解释它,所以这里暂且把这些参数放在一边。

#3: 带参数的一次性数据加载

比方说您想要加载一些依赖用户 ID 的数据,而信息来自一个提供数据流的 AuthManager:

△ 带参数的一次性数据加载 (LiveData)

使用 LiveData 时,您可以用类似这样的代码:

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:LiveData<String?>=authManager.observeUser().map{user->user.id}.asLiveData()valresult:LiveData<Result<Item>>=userId.switchMap{newUserId->liveData{emit(repository.fetchItem(newUserId))}}}

switchMap 是数据变换中的一种,它订阅了 userId 的变化,并且其代码体会在感知到 userId 变化时执行。

如非必须要将 userId 作为 LiveData 使用,那么更好的方案是将流式数据和 Flow 结合,并将最终的结果 (result) 转化为 LiveData。

class MyViewModel(authManager..., repository...) : ViewModel() {private val userId: Flow<UserId> = authManager.observeUser().map { user -> user.id }val result: LiveData<Result<Item>> = userId.mapLatest { newUserId ->repository.fetchItem(newUserId)}.asLiveData()
}

如果改用 Kotlin Flow 来编写,代码其实似曾相识:

△ 带参数的一次性数据加载 (StateFlow)

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:Flow<UserId>=authManager.observeUser().map{user->user.id}valresult:StateFlow<Result<Item>>=userId.mapLatest{newUserId->repository.fetchItem(newUserId)}.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.Loading)}

假如说您想要更高的灵活性,可以考虑显式调用 transformLatest 和 emit 方法:

val result = userId.transformLatest { newUserId ->emit(Result.LoadingData)emit(repository.fetchItem(newUserId))}.stateIn(scope = viewModelScope, started = WhileSubscribed(5000), initialValue = Result.LoadingUser //注意此处不同的加载状态)

#4: 观察带参数的数据流

接下来我们让刚才的案例变得更具交互性。数据不再被读取,而是被观察,因此我们对数据源的改动会直接被传递到 UI 界面中。

继续刚才的例子: 我们不再对源数据调用 fetchItem 方法,而是通过假定的 observeItem 方法获取一个 Kotlin 数据流。

若使用 LiveData,可以将数据流转换为 LiveData 实例,然后通过 emitSource 传递数据的变化。

△ 观察带参数的数据流 (LiveData)

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:LiveData<String?>=authManager.observeUser().map{user->user.id}.asLiveData()valresult=userId.switchMap{newUserId->repository.observeItem(newUserId).asLiveData()}}

或者采用更推荐的方式,把两个流通过 flatMapLatest 结合起来,并且仅将最后的输出转换为 LiveData:

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:Flow<String?>=authManager.observeUser().map{user->user?.id}valresult:LiveData<Result<Item>>=userId.flatMapLatest{newUserId->repository.observeItem(newUserId)}.asLiveData()}

使用 Kotlin 数据流的实现方式非常相似,但是省下了 LiveData 的转换过程:

△ 观察带参数的数据流 (StateFlow)

classMyViewModel(authManager...,repository...):ViewModel(){privatevaluserId:Flow<String?>=authManager.observeUser().map{user->user?.id}valresult:StateFlow<Result<Item>>=userId.flatMapLatest{newUserId->repository.observeItem(newUserId)}.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.LoadingUser)}

每当用户实例变化,或者是存储区 (repository) 中用户的数据发生变化时,上面代码中暴露出来的 StateFlow 都会收到相应的更新信息。

#5: 结合多种源: MediatorLiveData -> Flow.combine

MediatorLiveData 允许您观察一个或多个数据源的变化情况,并根据得到的新数据进行相应的操作。通常可以按照下面的方式更新 MediatorLiveData 的值:

val liveData1: LiveData<Int> = ...
val liveData2: LiveData<Int> = ...val result = MediatorLiveData<Int>()result.addSource(liveData1) { value ->result.setValue(liveData1.value ?: 0 + (liveData2.value ?: 0))
}
result.addSource(liveData2) { value ->result.setValue(liveData1.value ?: 0 + (liveData2.value ?: 0))
}

同样的功能使用 Kotlin 数据流来操作会更加直接:

valflow1:Flow<Int>=...valflow2:Flow<Int>=...valresult=combine(flow1,flow2){a,b->a+b}

此处也可以使用 combineTransform 或者 zip 函数。

通过 stateIn 配置对外暴露的 StateFlow

早前我们使用 stateIn 中间运算符来把普通的流转换成 StateFlow,但转换之后还需要一些配置工作。如果现在不想了解太多细节,只是想知道怎么用,那么可以使用下面的推荐配置:

valresult:StateFlow<Result<UiState>>=someFlow.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.Loading)

不过,如果您想知道为什么会使用这个看似随机的 5 秒的 started 参数,请继续往下读。

根据文档,stateIn 有三个参数:‍

@paramscope共享开始时所在的协程作用域范围@paramstarted控制共享的开始和结束的策略@paraminitialValue状态流的初始值当使用[SharingStarted.WhileSubscribed]并带有`replayExpirationMillis`参数重置状态流时,也会用到initialValue。

started 接受以下的三个值:

Lazily: 当首个订阅者出现时开始,在 scope 指定的作用域被结束时终止。

Eagerly: 立即开始,而在 scope 指定的作用域被结束时终止。

WhileSubscribed: 这种情况有些复杂 (后文详聊)。

对于那些只执行一次的操作,您可以使用 Lazily 或者 Eagerly。然而,如果您需要观察其他的流,就应该使用 WhileSubscribed 来实现细微但又重要的优化工作,参见后文的解答。

WhileSubscribed 策略

WhileSubscribed 策略会在没有收集器的情况下取消上游数据流。通过 stateIn 运算符创建的 StateFlow 会把数据暴露给视图 (View),同时也会观察来自其他层级或者是上游应用的数据流。让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程

WhileSubscribed 接受两个参数:

publicfunWhileSubscribed(stopTimeoutMillis:Long=0,replayExpirationMillis:Long=Long.MAX_VALUE)

超时停止

根据其文档:

stopTimeoutMillis 控制一个以毫秒为单位的延迟值,指的是最后一个订阅者结束订阅与停止上游流的时间差。默认值是 0 (立即停止)。

这个值非常有用,因为您可能并不想因为视图有几秒钟不再监听就结束上游流。这种情况非常常见——比如当用户旋转设备时,原来的视图会先被销毁,然后数秒钟内重建。

liveData 协程构建器所使用的方法是 添加一个 5 秒钟的延迟,即如果等待 5 秒后仍然没有订阅者存在就终止协程。前文代码中的 WhileSubscribed (5000) 正是实现这样的功能:

class MyViewModel(...) : ViewModel() {val result = userId.mapLatest { newUserId ->repository.observeItem(newUserId)}.stateIn(scope = viewModelScope, started = WhileSubscribed(5000), initialValue = Result.Loading)
}

这种方法会在以下场景得到体现:

用户将您的应用转至后台运行,5 秒钟后所有来自其他层的数据更新会停止,这样可以节省电量。

最新的数据仍然会被缓存,所以当用户切换回应用时,视图立即就可以得到数据进行渲染。

订阅将被重启,新数据会填充进来,当数据可用时更新视图。

数据重现的过期时间

如果用户离开应用太久,此时您不想让用户看到陈旧的数据,并且希望显示数据正在加载中,那么就应该在 WhileSubscribed 策略中使用 replayExpirationMillis 参数。在这种情况下此参数非常适合,由于缓存的数据都恢复成了 stateIn 中定义的初始值,因此可以有效节省内存。虽然用户切回应用时可能没那么快显示有效数据,但至少不会把过期的信息显示出来。

replayExpirationMillis 配置了以毫秒为单位的延迟时间,定义了从停止共享协程到重置缓存 (恢复到 stateIn 运算符中定义的初始值 initialValue) 所需要等待的时间。它的默认值是长整型的最大值 Long.MAX_VALUE (表示永远不将其重置)。如果设置为 0,可以在符合条件时立即重置缓存的数据。

从视图中观察 StateFlow

我们此前已经谈到,ViewModel 中的 StateFlow 需要知道它们已经不再需要监听。然而,当所有的这些内容都与生命周期 (lifecycle) 结合起来,事情就没那么简单了。

要收集一个数据流,就需要用到协程。Activity 和 Fragment 提供了若干协程构建器:

Activity.lifecycleScope.launch : 立即启动协程,并且在本 Activity 销毁时结束协程。

Fragment.lifecycleScope.launch : 立即启动协程,并且在本 Fragment 销毁时结束协程。

Fragment.viewLifecycleOwner.lifecycleScope.launch : 立即启动协程,并且在本 Fragment 中的视图生命周期结束时取消协程。

LaunchWhenStarted 和 LaunchWhenResumed

对于一个状态 X,有专门的 launch 方法称为 launchWhenX。它会在 lifecycleOwner 进入 X 状态之前一直等待,又在离开 X 状态时挂起协程。对此,需要注意对应的协程只有在它们的生命周期所有者被销毁时才会被取消

△ 使用 launch/launchWhenX 来收集数据流是不安全的

当应用在后台运行时接收数据更新可能会引起应用崩溃,但这种情况可以通过将视图的数据流收集操作挂起来解决。然而,上游数据流会在应用后台运行期间保持活跃,因此可能浪费一定的资源。

这么说来,目前我们对 StateFlow 所进行的配置都是无用功;不过,现在有了一个新的 API。

lifecycle.repeatOnLifecycle 前来救场

这个新的协程构建器 (自 lifecycle-runtime-ktx 2.4.0-alpha01 后可用) 恰好能满足我们的需要: 在某个特定的状态满足时启动协程,并且在生命周期所有者退出该状态时停止协程。

△ 不同数据流收集方法的比较

比如在某个 Fragment 的代码中:

onCreateView(...){viewLifecycleOwner.lifecycleScope.launch{viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED){myViewModel.myUiState.collect{...}}}}

当这个 Fragment 处于 STARTED 状态时会开始收集流,并且在 RESUMED 状态时保持收集,最终在 Fragment 进入 STOPPED 状态时结束收集过程。如需获取更多信息,请参阅: 使用更为安全的方式收集 Android UI 数据流。

结合使用 repeatOnLifecycle API 和上面的 StateFlow 示例可以帮助您的应用妥善利用设备资源的同时,发挥最佳性能。

△ 该 StateFlow 通过 WhileSubscribed(5000) 暴露并通过 repeatOnLifecycle(STARTED) 收集

注意: ref="https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.android.google.cn%2Ftopic%2Flibraries%2Fdata-binding%2Fobservability%23stateflow">近期在 Data Binding 中加入的 StateFlow 支持 使用了 launchWhenCreated 来描述收集数据更新,并且它会在进入稳定版后转而使用 repeatOnLifecyle

对于数据绑定,您应该在各处都使用 Kotlin 数据流并简单地加上 asLiveData() 来把数据暴露给视图。数据绑定会在 lifecycle-runtime-ktx 2.4.0 进入稳定版后更新。

总结

通过 ViewModel 暴露数据,并在视图中获取的最佳方式是:

✔️ 使用带超时参数的 WhileSubscribed 策略暴露 StateFlow。[示例 1]

✔️ 使用 repeatOnLifecycle 来收集数据更新。[示例 2]

如果采用其他方式,上游数据流会被一直保持活跃,导致资源浪费:

❌ 通过 WhileSubscribed 暴露 StateFlow,然后在 lifecycleScope.launch/launchWhenX 中收集数据更新。

❌ 通过 Lazily/Eagerly 策略暴露 StateFlow,并在 repeatOnLifecycle 中收集数据更新。

当然,如果您并不需要使用到 Kotlin 数据流的强大功能,就用 LiveData 好了 :)
ManuelWojtekYigit、Alex Cook、FlorinaChris 致谢!

Android核心知识点笔记(点击最下方卡片获取)

Android开发核心知识点笔记

Android Framework核心知识点笔记

Android Flutter核心知识点笔记与实战详解

音视频开发笔记,入门到高级进阶

性能优化核心知识点笔记

Android开发高频面试题,25个知识点整合

Android开发核心架构知识点笔记

http://www.yayakq.cn/news/100776/

相关文章:

  • 海外制作网站安徽省建设工程信息网关闭了吗
  • 快速网站排名提升工具网站的友情连接怎么做
  • 做网站申请域名的流程刘娇娇做网站骗钱的
  • 深圳网站建设-龙华信科企业网站开发创意
  • 企业网站视频栏目建设方案国家建设网站
  • 自助建站视频网站有人有片资源吗免费的视频
  • 有哪些网站是cmsvuepress wordpress
  • 汽车网站更新怎么做给非吸公司建设网站
  • 电脑装机网站html5企业网站模板
  • 物流网络规划名词解释烟台优化网站建设
  • 中国住房和建设部网站中国电信网站备案
  • dedecms手机网站制作当阳建设中学网站
  • 门户网站建设的成果做那个男女的视频网站
  • 手机下载视频网站模板下载失败电商网站运营团队建设方案模板
  • 个人网站需要建站群吗消防工程师证怎么考
  • 洛阳网站建设价格建筑网课平台哪个好
  • 专业的营销型网站建设广州网站开发工程师
  • 郑州 外包网站计算机培训机构
  • 网站公司做的网站有最字dede网站wap
  • 平板电脑做网站吗二级学院网站制度建设
  • 专业做旅游网站的公司游戏推广员是做什么的
  • 广州网站建设推广公司惠城网站建设服务
  • 网站前端浏览器兼容如何做陈巴尔虎旗网站建设
  • 国外做电商网站有哪些官网建立
  • 织梦网站地图如何做php学校网站源码
  • dede移动端网站源码网站设计要点 优帮云
  • wordpress只能显示字上海市网站seo
  • 精品网站建设费用 尖端磐石网络h5制作官网登录
  • 网站建设上的新闻建设企业银行登录
  • 网站编辑兼职网站建设岗位