Kotlin StateFlow 搜索功能的实践 DB + NetWork

简介: 这篇文章主要来分析一下 PokemonGo 搜索功能的实践

image.png


前言



在之前分享过一篇文章 Google 推荐在 MVVM 架构中使用 Kotlin Flow ,在这篇文章中分析了如何在 MVVM 架构中使用 Kotlin Flow,以及 Kotlin Flow 为我们解决了以下问题:


  • LiveData 是一个生命周期感知组件,最好在 View 和 ViewModel 层中使用它,如果在 Repositories 或者 DataSource 中使用会有几个问题
  • 它不支持线程切换,其次不支持背压,也就是在一段时间内发送数据的速度 > 接受数据的速度,LiveData 无法正确的处理这些请求
  • 使用 LiveData 的最大问题是所有数据转换都将在主线程上完成
  • RxJava 虽然支持线程切换和背压,但是 RxJava 那么多傻傻分不清楚的操作符,实际上在项目中常用的可能只有几个例如 ObservableFlowable  、 Single 等等,如果我们不去了解背后的原理,造成内存泄露是很正常的事,大家可以从 StackOverflow 上查看一下,有很多因为 RxJava 造成内存泄露的例子
  • RxJava 入门的门槛很高,学习过的朋友们,我相信能够体会到从入门到放弃是什么感觉
  • 解决回调地狱的问题


而相对于以上的不足,Flow 有以下优点:


  • Flow 支持线程切换、背压
  • Flow 入门的门槛很低,没有那么多傻傻分不清楚的操作符
  • 简单的数据转换与操作符,如 map 等等
  • Flow 是对 Kotlin 协程的扩展,让我们可以像运行同步代码一样运行异步代码,使得代码更加简洁,提高了代码的可读性
  • 易于做单元测试


而这篇文章主要来分析一下 PokemonGo 搜索功能的实践,主要包含以下几个方面的内容:


  • Kotlin Flow 是什么?以及如何使用?
  • 如何区分末端操作符还是中间操作符?
  • Kotlin Channel 是什么?以及如何使用?
  • Kotlin Channel 都有那几种类型?
  • BroadcastChannels 是什么?以及如何在项目中使用?
  • StateFlow 是什么?以及如何在项目中使用?
  • Kotlin 常用操作符 debouncefilterflatMapLatestdistinctUntilChanged 解析?


之前有很多朋友跟我反馈,如何使用 Flow 实现搜索功能,所以我在 PokemonGo 项目中增加了两种搜索场景,分别演示 BroadcastChannelsStateFlow 的用法。


  • 使用 ConflatedBroadcastChannel 实现 DB 搜索
  • 使用 StateFlow 实现 NetWork 搜索


在分析这两种实现方式之前,需要先了解几个基本概念, Flow 和 Channel 是什么,以及常用的操作符 debouncefilterflatMapLatestdistinctUntilChanged 等等的使用,Flow 和 Channel 是一个比较大的概念,后面我会花好几篇文章来分析它们,本文只会概述它们之间的区别。


Kotlin Flow 是什么



先来看看 Kotlin 官方文档是如何介绍 Flow


image.png


将上面这段话,简单的总结一下:


  • Flow 是非阻塞的,以挂起的方式执行,只有遇到末端操作符,才会触发所有操作的执行
  • 所有操作都在相同的代码块内顺序执行
  • 发射出来的值都是顺序执行的,只有在某一时刻结束(遇到 末端操作符 或者出现异常)
  • map , filter , take , zip 等等是中间操作符,collect , collectLatest , single , reduce , toList 等等末端操作符
  • 中间操作符构建了一个待执行的调用链,如下图所示:


image.png


不阻塞,以挂起的方式执行 :也就是协程作用域被挂起, 当前线程中协程作用域之外的代码不会阻塞


接下来我们来看一段示例:


suspend fun printValue() = flow<Int> {
    for (index in 1..10) {
        emit(index)
    }
}.map { it -> it * it } // map, filter, take, zip 等等是中间操作符
.filter { it -> it > 5 } 
.toList() // 只有遇到末端操作符 collect, collectLatest,single, reduce, toList 等等才会触发所有操作的执行


  • 遇到中间操作符,并不会执行任何操作,也不会挂起函数本身,这些操作符构建了一个待执行的调用链
  • 末端操作符是可挂起函数,遇到末端操作符会触发所有操作的执行


如何区分末端操作符还是中间操作符


区分末端操作符还是中间操作符,可以按照是否是挂起函数来区分,我个人觉得按照挂起函数来区分,方便去记忆上面提到的 Flow 的几个特点,当然也可以按照其他方式来区分,我们一起来分析一下源码。


// 中间操作符是 Flow 的扩展函数,它们最后都是通过 emit 来发射数据
public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
    if (predicate(value)) return@transform emit(value)
}
// 末端操作符是一个挂起函数
// 末端操作符无论是 collectLatest,single, reduce, toList 最后都是调用 collect
public suspend fun <T> Flow<T>.toList(destination: MutableList<T> = ArrayList()): List<T> = toCollection(destination)
public suspend fun <T, C : MutableCollection<in T>> Flow<T>.toCollection(destination: C): C {
    collect { value ->
        destination.add(value)
    }
    return destination
}


  • 中间操作符是 Flow 的扩展函数,它们最后都是通过 emit 来发射数据
  • 末端操作符是一个挂起函数
  • 末端操作符无论是 collectLatest , single , reduce , toList 最后都是调用 collect


Kotlin Channel 是什么



来看看 Kotlin 官方文档是如何介绍 Channel


image.png


将上面这段话,简单的总结一下:


  • Channel 是非阻塞的,它用于发送方 (SendChannel) 和接收方 (ReceiveChannel) 之间通信
  • Channel 实现了 SendChannel 和 ReceiveChannel 接口,所以既可以发送数据又可以接受数据
  • Channel 和 Java 中的 BlockingQueue 类似,不同之处在于 BlockingQueue 是阻塞的,而 Channel 是挂起的
  • 发送方 (SendChannel) 和接收方 (ReceiveChannel) 之间通过缓冲区进行同步的,如下图所示:


image.png


  • 通过发送方 (SendChannel) 将数据发送到缓冲区
  • 通过接收方 (ReceiveChannel) 从缓冲区获取数据
  • 发送方 (SendChannel) 和 接收方 (ReceiveChannel) 之间有一个通道,也就是缓冲区
  • 缓冲区的作用帮我们同步发送方 (SendChannel) 和 接收方 (ReceiveChannel) 发送和接受的数据,也就意味着多个协程可以向同一个 channel 发送数据, 一个 channel 的数据也可以被多个协程接收


我们来实现一个简易的消息发送和接受的例子:


val channel = Channel<Int>()
// 接受消息
suspend fun receiveEvent() {
    coroutineScope {
        while (!channel.isClosedForReceive) {
            // receive()方法异步获取元素,如果缓冲区是空,receive() 调用者将被挂起,直到一个新值被发送到缓冲区
            // receive() 是一个挂起函数,用于同步发送方和接收方的一种机制
             channel.receive()
            // poll()方法同步获取一个元素,如果缓冲区是空的,则返回null
            // channel.poll()
        }
    }
}
// 发送消息
suspend fun postEvent() {
    coroutineScope {
        if (!channel.isClosedForSend) {
            (1..10).forEach {
                // 如果缓冲区没有满,则立即添加元素,
                // 如果缓冲区满了调用者会被挂起
                // send() 是一个挂起函数,用于同步发送方和接收方的一种机制
                channel.send(it)
                // offer():如果缓冲区存在并且没有满立即向缓冲区添加一个元素
                // 如果添加成功会返回true, 失败会返回 false
                // channel.offer(it)
            }
        }
    }
}


正如你所看到的 发送接受 都有两个方法,分别来分析一下他们的区别。


send() 和 offer() 的区别:


  • send(element: E) :如果缓冲区没有满,则立即添加元素, 如果缓冲区满了调用者会被挂起,send() 方法是一个挂起函数,用于同步发送方和接收方的一种机制
  • offer(element: E): Boolean :如果缓冲区存在并且没有满立即向缓冲区添加一个元素,添加成功会返回 true, 失败会返回 false

receive() 和 poll() 的区别:

  • receive(): E :异步获取元素,如果缓冲区是空时调用者会被挂起,直到一个新值被发送到缓冲区,receive() 方法是一个挂起函数,用于同步发送方和接收方的一种机制
  • poll(): E?:用于同步获取一个元素,如果缓冲区是空的,则返回 null


Flow 与 Channel 的区别:


  • Flow :中间操作符 (map , filter 等等) 会构建了一个待执行的调用链,只有遇到末端操作符 (collect , toList 等等) 才会触发所有操作的执行,所以 Flow 也被称为冷数据流
  • Channel :发送方 (SendChannel) 发送数据,并不依赖于接受方(ReceiveChannel),所以 Channel 也被称为热数据流


Channel 的不同类型


Channel 对应着有四种不同的类型:


  • RendezvousChannel :这是默认的类型,大小为 0 的缓冲区,只有当 send() 方法和 receive() 方法都调用的时候,元素才会从发送方传输到接收方,否则将会被挂起
  • LinkedListChannel :通过 Channel.Factory.CONFLATED 会创建一个容量无限的缓冲区 (受限于内存的大小) ,send() 方法远不会挂起,offer() 方法始终返回 true
  • ConflatedChannel :最多缓冲一个元素,新元素会覆盖掉旧元素,只会接收最后发送的元素,之前的元素都会丢失,send() 方法永远不会挂起,offer() 方法始终返回 true
  • ArrayChannel :通过 Channel.Factory.BUFFERED 或者 指定大小 会创建一个固定容量的数组缓冲区,send() 方法仅在缓冲区满时挂起,receive() 方法仅在缓冲区为空时挂起


创建四种不同类型 channel 的方式:


val rendezvousChannel = Channel<Int>()
val linkedListChannel = Channel<Int>(Channel.Factory.UNLIMITED)
val conflatedChannel = Channel<Int>(Channel.Factory.CONFLATED)
// 指定数字 或者 通过 Channel.Factory.BUFFERED 都会创建创建一个固定容量的数组缓冲区
val bufferedChannel = Channel<Int>(Channel.Factory.BUFFERED) // 创建默认容量的数组缓冲区
val arrayChannel = Channel<Int>(30) // 创建指定容量的数组缓冲区


上面创建 channel 的方式,对应的源码:


public fun <E> Channel(capacity: Int = RENDEZVOUS): Channel<E> =
    when (capacity) {
        RENDEZVOUS -> RendezvousChannel()
        UNLIMITED -> LinkedListChannel()
        CONFLATED -> ConflatedChannel()
        BUFFERED -> ArrayChannel(CHANNEL_DEFAULT_CAPACITY)
        else -> ArrayChannel(capacity)
    }


BroadcastChannels 是什么


来看看 Kotlin 官方文档是如何介绍 BroadcastChannels


网络异常,图片无法展示
|


  • BroadcastChannels 是非阻塞的,它用于发送方 (SendChannel) 和接收方 (ReceiveChannel) 之间通信
  • BroadcastChannels 实现了 SendChannel 接口,所以只可以发送数据
  • BroadcastChannels 提供了 openSubscription 方法,会返回一个新的 ReceiveChannel,可以从缓冲区获取数据
  • 通过 BroadcastChannels 发送的数据,所有接收方 (ReceiveChannel) 都会收到,如下图所示


image.png


BroadcastChannels 是一个接口,而它的子类有 ConflatedBroadcastChannel、ArrayBroadcastChannel,这里主要介绍一下 ConflatedBroadcastChannel,ConflatedBroadcastChannel 重写了 openSubscription 方法。


public override fun openSubscription(): ReceiveChannel<E> {
    val subscriber = Subscriber(this)
    ...... // 省略很多无关的代码
    return subscriber
}


  • openSubscription 方法返回一个 ReceiveChannel 作为接受者
  • openSubscription 方法内,创建了一个 Subscriber 的实例


Subscriber 其实是 ConflatedBroadcastChannel 的内部类,它实现了 ReceiveChannel 接口。


private class Subscriber<E>(
    private val broadcastChannel: ConflatedBroadcastChannel<E>
) : ConflatedChannel<E>(), ReceiveChannel<E>


正如你所见 Subscriber 继承 ConflatedChannel 同时实现了 ReceiveChannel 接口,而 ConflatedChannel 在上文介绍过了,最多缓冲一个元素,新元素会覆盖掉旧元素,只会接收最后发送的元素,之前的元素都会丢失,所以 ConflatedBroadcastChannel 适合用来实现搜索相关的功能,因为用户只对最后一次搜索结果感兴趣。


注意: StateFlow 将会取代 ConflatedBroadcastChannel 下文有介绍


使用 ConflatedBroadcastChannel 实现 DB 搜索


我在 PokemonGo 项目中增加了两种搜索场景,分别通过 BroadcastChannelsStateFlow 来实现,通过 ConflatedBroadcastChannel 实现 DB 搜索,只需要两步


1.在 Activity 中监听 ConflatedBroadcastChannel 的变化

src/main/java/com/hi/dhl/pokemon/ui/main/MainActivity.kt


// searchView 是一个 AppCompatEditText,当然你可以使用 androidx.appcompat.widget.SearchView,或者其他
searchView.addTextChangedListener {
                val result = it.toString()
                // 调用 queryParamterForDb 方法过滤用户的输入,并查询数据库
                mViewModel.queryParamterForDb(result)
            }
// 监听查询结果
mViewModel.searchResultForDb.observe(this, Observer {
    mPokemonAdapter.submitData(lifecycle, it)
})


  • 接受用户输入的数据,并调用 queryParamterForDb 方法过滤用户的输入,然后查询数据库
  • 通过 searchResultForDb.observe 方法监听查询结果


2. 在 MainViewModel 中实现 queryParamterForDb 方法src/main/java/com/hi/dhl/pokemon/ui/main/MainViewModel.kt


// 根据关键词搜索
fun queryParamterForDb(paramter: String) = mChanncel.offer(paramter)
// 使用 ConflatedBroadcastChannel 进行搜索
val searchResultForDb = mChanncel.asFlow()
    // 避免在单位时间内,快输入造成大量的请求
    .debounce(200)
    //  避免重复的搜索请求。假设正在搜索 dhl,用户删除了 l  然后输入 l。最后的结果还是 dhl。它就不会再执行搜索查询 dhl
    // distinctUntilChanged 对于 StateFlow 任何实例是没有效果的
    .distinctUntilChanged()
    .flatMapLatest { search -> // 只显示最后一次搜索的结果,忽略之前的请求
        pokemonRepository.fetchPokemonByParameter(search).cachedIn(viewModelScope)
    }
    .catch { throwable ->
        //  异常捕获
    }.asLiveData()


  • 通过 mChanncel.offer 发送数据
  • 通过 mChanncel.asFlow() 方法,将 Channel 转换为 Flow 并调用 debouncedistinctUntilChangedflatMapLatest 过掉用户的输入数据,这些操作符在后文会详细分析
  • 最后查询数据库,返回结果,项目中使用的是通过 Paging3 查询本地数据库,关于如何实现可以查看另外一篇文章 Jetpack 成员 Paging3 数据实践以及源码分析(一)


重点: 在 Kotlin coroutines library (1.3.6) 版本中增加了一个新类 StateFlow,它的设计和 ConflatedBroadcastChannel 相同,将来计划完全取代 ConflatedBroadcastChannel


StateFlow 是什么


在前面的内容提到了很多次 StateFlow,那么 StateFlow 是什么,它与 Flows 和 Channels 有什么关系呢,来看看 Kotlin 官方文档是如何介绍 StateFlow


image.png


将上面这段话,简单的总结一下:


  • StateFlow 实现了 Flow 接口,它仅仅表示一种可读的状态,它的值是不变的,用于外部调用


public interface StateFlow<out T> : Flow<T> {
    public val value: T //  val 关键字表示不可变的
}


  • StateFlow 提供了一个可变的版本 MutableStateFlow,它的值是可变的,用于内部调用


public interface MutableStateFlow<T> : StateFlow<T> {
    public override var value: T // var 表示可变的
}


  • StateFlow 与 Flow 的不同之处在于,StateFlow 仅仅表示一种状态,不依赖于特定的上下文,而 Flow 操作执行是在 CoroutineScope 内的,换句话说 StateFlow 不需要在协程的作用域内,它也可以执行


刚才我们提到 StateFlow 的出现是为了取代 ConflatedBroadcastChannel,那么它与 ConflatedBroadcastChannel 有什么不同之处:


  • StateFlow 实现更加简单,不需要实现所有 Channel API,而 ConflatedBroadcastChannel 在其内部封装了 ConflatedChannel 和 BroadcastChannels
  • StateFlow 内部有个变量 value,无论任何时候都可以安全的访问
  • StateFlow 实现读写分离,StateFlow 用来读而 MutableStateFlow 用来写
  • StateFlow 内部使用 Any.equals 来比较新值与旧值,和 distinctUntilChanged 方式相同,所以在 StateFlow 上应用 distinctUntilChanged 是没有效果的
    StateFlow 源码:


if (oldState == newState) return // 如果值没有改变,不会做任何事


  • distinctUntilChanged 源码


public fun <T, K> Flow<T>.distinctUntilChangedBy(keySelector: (T) -> K): Flow<T> =
    distinctUntilChangedBy(keySelector = keySelector, areEquivalent = { old, new -> old == new })


使用 StateFlow 实现 NetWork 搜索


StateFlow 和 ConflatedBroadcastChannel 一样,实现搜索功能只需要两步


1.在 Activity 中监听 ConflatedBroadcastChannel 的变化src/main/java/com/hi/dhl/pokemon/ui/main/MainActivity.kt


// searchView 是一个 AppCompatEditText,当然你可以使用 androidx.appcompat.widget.SearchView 或者其他
searchView.addTextChangedListener {
    val result = it.toString()
    // 调用 queryParamterForNetWork 方法过滤用户的输入,并查询网络
    mViewModel.queryParamterForNetWork(result)
}
mViewModel.searchResultMockNetWork.observe(this, Observer {
    // 网络搜索回调监听
})


  • 接受用户输入的数据,并调用 queryParamterForNetWork 方法过滤用户的输入,通过网络查询关键字
  • 通过 searchResultMockNetWork.observe 方法监听查询结果


2. 在 MainViewModel 中实现 queryParamterForNetWork 方法src/main/java/com/hi/dhl/pokemon/ui/main/MainViewModel.kt


// 根据关键词搜索
fun queryParamterForNetWork(paramter: String) {
    _stateFlow.value = paramter
}
// 因为没有合适的搜索接口,在这里模拟进行网络搜索
val searchResultMockNetWork =
    // 避免在单位时间内,快输入造成大量的请求
    stateFlow.debounce(200)
        .filter { result ->
            if (result.isEmpty()) { // 过滤掉空字符串等等无效输入
                return@filter false
            } else {
                return@filter true
            }
        }
        .flatMapLatest { // 只显示最后一次搜索的结果,忽略之前的请求
            // 网络请求,这里替换自己的实现即可
        }
        .catch { throwable ->
            //  异常捕获
        }
        .asLiveData()


  • 通过 _stateFlow.value 更新数据
  • 调用 debouncefilterflatMapLatest 等等操作符过滤掉无效的请求


常用操作符解析



PokemonGo 项目中使用 debouncefilterflatMapLatestdistinctUntilChanged 等等操作符,一起来详细的分析一下这些操作符的含义,以及如何使用。


debounce


image.png


debounce 也叫做防抖动函数,当用户在很短的时间内输入 "d","dh","dhl",但是用户可能只对 "dhl" 的搜索结果感兴趣,因此我们必须舍弃 "d","dh" 过滤掉不需要的请求,针对于这个情况,我们可以使用 debounce 函数,在指定时间内出现多个字符串,debounce 始终只会发出最后一个字符串,我们来看个例子。


val result = flow {
    emit("h")
    emit("i")
    emit("d")
    delay(90)
    emit("dh")
    emit("dhl")
}.debounce(200).toList()
println(result) // 最后输出:dhl


filter


image.png


filter 操作符用于过滤不需要的字符串,在 PokemonGo 项目中只过滤了空字符串,我们来看个例子。


val result = flow {
    emit("h")
    emit("i")
    emit("d")
    delay(90)
    emit("dh")
    emit("dhl")
}.filter { result ->
    if (!result.equals("dhl")) {
        return@filter false
    } else {
        return@filter true
    }
}.toList()
println(result) // 最后输出:dhl


flatMapLatest


image.png


flatMapLatest 避免向用户展示不需要的结果,只提供最后一个搜索查询(最新)的结果,例如,正在查询 "dh",然后用户输入 "dhl", 这个时候用户对 "dh" 的结果不感兴趣,可能只对 "dhl" 的结果感兴趣,这个时候可以使用 flatMapLatest,我们来看个例子。


flow {
    emit("dh")
    emit("dhl")
}.flatMapLatest { value ->
    flow<String> {
        delay(100)
        println("collected $value") // 最后输出 collected dhl
    }
}.collect()


注意:flatMapLatest 在 Kotlin coroutines library (1.3.20) 以下版本使用会出现以下错误。


IllegalStateException crash: call to 'resume' before 'invoke' with coroutine


Kotlin 团队在 Kotlin coroutines library (1.3.20) 以上修复了这个问题,如果出现这个问题,将版本升级到 1.3.20 以上即可 issues 地址


DistinctUntilChanged


image.png


  • distinctUntilChanged 操作符用来过滤掉重复的请求,只有当前值与最后一个值不同时才将其发出,我们来看个例子。


val result = flow {
    emit("d")
    emit("d")
    emit("d")
    emit("d")
    emit("dhl")
    emit("dhl")
    emit("dhl")
    emit("dhl")
}.distinctUntilChanged().toList()
println(result) // 输出 [d, dhl]


  • StateFlow 内部已经实现了类似于 distinctUntilChanged 操作符的功能,因此 distinctUntilChanged 应用在 StateFlow 上是没有效果的


我们一起来分析 distinctUntilChanged 操作符源码是如何实现的


public fun <T> Flow<T>.distinctUntilChanged(): Flow<T> =
    when (this) {
        is StateFlow<*> -> this
        else -> distinctUntilChangedBy { it }
    }


  • distinctUntilChanged 是 Flow 的扩展函数
  • 如果当前对象是 StateFlow,直接返回调用者本身
  • 如果不是 StateFlow 就会调用 distinctUntilChangedBy 方法


public fun <T, K> Flow<T>.distinctUntilChangedBy(keySelector: (T) -> K): Flow<T> =
    distinctUntilChangedBy(keySelector = keySelector, areEquivalent = { old, new -> old == new })


最后会调用 areEquivalent 方法进行比较,会过滤掉所有相同值的


全文到这里就结束了,效果图如下所示,如果效果图无法查看,请点击这里查看 效果图


image.png


文章中提到的 PokemonGo(神奇宝贝) 是基于 Jetpack + MVVM + Data Mapper + Repository + Paging3 + App Startup + Hilt + Kotlin Flow + Motionlayout + Coil 等等技术综合实战项目,点击这里前往查看


参考文献




结语



应小伙伴们的建议,公众号开通了:ByteCode , 方便查看一系列的文章。致力于分享一系列 Android 系统源码、逆向分析、算法、译文、Kotlin、Jetpack 源码相关的文章,如果这篇文章对你有帮助给个 star,欢迎一起来学习,在技术的道路上一起前进。


正在建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,仓库持续更新中,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请仓库右上角帮我点个赞。


算法


由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。


  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……


每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路、时间复杂度和空间复杂度,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一起来学习,期待与你一起成长。


Android 10 源码系列


正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库。



Android 应用系列



精选译文


目前正在整理和翻译一系列精选国外的技术文章,不仅仅是翻译,很多优秀的英文技术文章提供了很好思路和方法,每篇文章都会有译者思考部分,对原文的更加深入的解读,可以关注我 GitHub 上的 Technical-Article-Translation,文章都会同步到这个仓库。




目录
相关文章
|
6月前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin协程的实践与优化策略
【5月更文挑战第30天】 在移动开发领域,性能优化始终是关键议题之一。特别是对于Android开发者来说,如何在保证应用流畅性的同时,提升代码的执行效率,已成为不断探索的主题。近年来,Kotlin语言凭借其简洁、安全和实用的特性,在Android开发中得到了广泛的应用。其中,Kotlin协程作为一种新的并发处理机制,为编写异步、非阻塞性的代码提供了强大工具。本文将深入探讨Kotlin协程在Android开发中的应用实践,以及如何通过协程优化应用性能,帮助开发者构建更高效的Android应用。
|
6月前
|
移动开发 API Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第17天】在移动开发领域,性能优化和流畅的用户体验一直是开发者追求的目标。针对Android平台,Kotlin语言凭借其简洁性和功能丰富性成为了许多开发者的首选。其中,Kotlin协程作为异步编程的强大工具,为处理并发任务提供了轻量级的解决方案。本文深入探讨了Kotlin协程的核心优势,并通过实例分析其在Android开发中的应用,旨在帮助开发者提升应用的性能和响应能力。
|
3月前
|
前端开发 编译器 测试技术
Kotlin Multiplatform 跨平台开发的优化策略与实践
本文深入讲解Kotlin Multiplatform(KMP)的优化策略与实践。KMP是由JetBrains推出的开源技术,允许跨平台共享代码同时保持原生优势。文章覆盖KMP核心概念、性能优化技巧(如代码结构优化、利用`expect`/`actual`关键字、Kotlin/Native性能特性等),以及在移动、桌面和Web应用的实际案例分析。此外,还介绍了如何利用KMP生态系统工具进行快速开发,并展望了KMP的未来发展。
66 0
|
5月前
|
消息中间件 缓存 API
Kotlin中的StateFlow和SharedFlow有什么区别?
- `StateFlow`持有一个最新的状态值,适合UI状态管理,新订阅者立即收到当前值。 - `SharedFlow`是通用热流,用于事件总线,不保留最新状态但可配置重播。 - `StateFlow`继承自`SharedFlow`,更专注于状态管理,而`SharedFlow`提供事件处理灵活性。 - 示例中展示了如何`emit`新值和`collect`变化。 - 选择`StateFlow`用于单最新状态共享,`SharedFlow`则适用于需要事件历史或定制策略的场景。 关注公众号“AntDream”了解更多内容!
69 1
|
5月前
|
安全 Kotlin
Kotlin中的安全导航操作符?.、空合并运算符?:以及let函数的实践与理解
Kotlin中的安全导航操作符?.、空合并运算符?:以及let函数的实践与理解
|
6月前
|
API 调度 Android开发
打造高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第27天】在移动开发领域,性能优化和响应速度是衡量应用质量的关键因素。随着Kotlin语言的普及,协程作为其核心特性之一,为Android开发者提供了一种全新的并发处理方式。本文深入探讨了Kotlin协程在Android应用开发中的优势,并通过实例演示如何在实际项目中有效利用协程提升应用性能和用户体验。
|
6月前
|
移动开发 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第21天】在移动开发领域,性能优化和流畅的用户体验是至关重要的。随着Kotlin语言在Android平台的广泛采纳,其并发处理的强大工具—协程(Coroutines),已成为提升应用响应性和效率的关键因素。本文将深入分析Kotlin协程的核心原理,探讨其在Android开发中的优势,并通过实例演示如何有效利用协程来优化应用性能,打造更加流畅的用户体验。
66 4
|
6月前
|
移动开发 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第17天】 在移动开发领域,尤其是针对Android平台,性能优化和流畅的用户体验始终是开发者追求的目标。近年来,Kotlin语言因其简洁性和功能性而成为Android开发的热门选择。其中,Kotlin协程作为一种轻量级的线程管理方案,为编写异步代码提供了强大支持,使得处理并发任务更加高效和容易。本文将深入探讨Kotlin协程的核心优势,并通过具体实例展示如何在Android应用中有效利用协程来提升性能和用户体验。
|
6月前
|
物联网 区块链 Android开发
构建高效Android应用:Kotlin与Jetpack的实践之路未来技术的融合潮流:区块链、物联网与虚拟现实的交汇点
【5月更文挑战第30天】 在移动开发领域,效率和性能始终是开发者追求的核心。随着技术的不断进步,Kotlin语言以其简洁性和现代化特性成为Android开发的新宠。与此同时,Jetpack组件为应用开发提供了一套经过实践检验的库、工具和指南,旨在简化复杂任务并帮助提高应用质量。本文将深入探索如何通过Kotlin结合Jetpack组件来构建一个既高效又稳定的Android应用,并分享在此过程中的最佳实践和常见陷阱。
|
6月前
|
运维 监控 Android开发
构建高效自动化运维系统的策略与实践构建高效Android应用:Kotlin协程的实践指南
【5月更文挑战第29天】随着信息技术的迅猛发展,企业IT基础设施变得日益复杂,传统的手动运维模式已难以满足高效率、高稳定性的要求。本文将深入探讨如何通过自动化工具和策略来构建一个高效的自动化运维系统。文中不仅分析了自动化运维的必要性,还详细介绍了实现过程中的关键步骤,包括监控、配置管理、故障响应等,并结合实际案例分析其效果,以期为读者提供一套行之有效的自动化运维解决方案。