IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android app 中这样用flow更方便-巧用flow实现polling -> 正文阅读

[移动开发]Android app 中这样用flow更方便-巧用flow实现polling

背景

? ? ? ? 在app开发过程中,实现polling逻辑也是很常见的。当然在移动端应用使用polling处理会影响应用的性能。比如polling处理增加了网络请求的次数,服务端压力增加。polling处理也消耗了更多的网络流量。但是应用polling的场景还是有的。有时是否选择polling要考虑很多综合的因素,比如我们可以使用长连接替代polling,但是长连接在服务端和客户端的开发成本相对要更高些,如果polling只是实现类似的跟帖等功能,我们完全可以使用polling实现,而不是选择代价更高的长连接方案。下面会分使用flow和不使用flow两种方式实现polling并对比两种方式的优缺点。

不使用flow

? ? ? ? 我们使用线程处理polling请求,首先我们定义了一个polling thread。

    class PollingThread: Thread() {
        override fun run() {
            var successBlock : (PollingData)->Unit = {
                Log.d("PollingThread","successBlock $it")
            }
            var failBlock:(Exception)->Unit ={
                Log.d("PollingThread","failBlock $it")
            }
            while (isInterrupted) {
                pollingApi.call(successBlock, failBlock)
                Thread.sleep(5000)
            }
        }
    }

????????在run方法中实现了polling接口的调用,并且接口的调用在while循环中。这里假设polling的时间间隔是5秒钟,所以这里调用线程的sleep方法暂停线程的执行,5秒后再次调用polling接口。polling接口的调用是异步过程,所以这里设置了两个回调,一个用于接收成功的数据,一个用于接收失败的异常。如果在回调中更新了画面,我们还要考虑如何保证回调在ui线程执行,并且回调中不更新消失的页面元素。

 class PollingThread(val lifecycleOwner: LifecycleOwner): Thread() {
        override fun run() {
            var successBlock : (PollingData)->Unit = {
                Handler(Looper.getMainLooper()).post {
                    if(lifecycleOwner.lifecycle.currentState >= Lifecycle.State.RESUMED) {
                        Log.d("PollingThread", "successBlock $it")
                    }
                }
            }
            var failBlock:(Exception)->Unit ={
                Handler(Looper.getMainLooper()).post {
                    if(lifecycleOwner.lifecycle.currentState >= Lifecycle.State.RESUMED) {
                        Log.d("PollingThread", "failBlock $it")
                    }
                }
            }
            while (isInterrupted) {
                pollingApi.call(successBlock, failBlock)
                Thread.sleep(5000)
            }
        }
    }

? ? ? ? 这段代码增加了回调的线程切换和ui画面有效判断。使用Handler切换线程到ui线程,lifecycler判断ui画面的有效性。

? ? ? ? polling线程已经定义完成,下一步我们还要在适当的时机启动polling线程和停止polling线程。

    var pollingThread:PollingThread? = null

    override fun onResume() {
        super.onResume()
        pollingThread = PollingThread().run { 
            start()
            this
        }
    }

    override fun onPause() {
        super.onPause()
        pollingThread?.interrupt()
        pollingThread = null
    }

? ? ? ? 这里定义了一个变量pollingThread用于保存启动的polling线程,我们在onResume方法中启动polling线程,在onPause方法中停止线程。经过这样处理后polling就可以工作了。

使用flow

? ? ? ? 首先我们需要定义一个polling flow。

    private val pollingFlow = flow {
        while (true) { 
            emit(serverApi.getPollingData())
           delay(2000)
        }
    }

? ? ? ? 在flow中使用了while循环实现无限轮训,请求的网络接口被定义成了挂起函数,轮训间隔通过协程的delay方法实现。对比不使用flow的方式,polling flow 有自己的一些优势。①无线轮训控制更加简单,不需要复杂逻辑判断,因为flow 中的轮训逻辑中有挂起函数的调用,当收集polling flow的协程被取消时,挂起函数会抛出取消异常,这样就达到了轮训逻辑控制的目的了。②由于调用服务器的接口函数是挂起函数,所以这里避免了使用callback 方法。

? ? ? ? 我们如何控制线程切换,如何轮训异常呢?

 private val pollingFlow = flow {
        while (true) {
            emit(serverApi.getPollingData())
            delay(5000)
        }
    }.flowOn(Dispatchers.IO).retryWhen { cause, attempt ->
        Log.d("polling flow ", "retryWhen cause $cause attempt $attempt")
        delay(5000)
        true
    }.onEach {
        Log.d("polling flow ", "onEach $it")
    }




 lifecycleScope.launchWhenResumed { pollingFlow.collect() }

? ? ? ? 我们可以通过flowOn方法切换线程,保证了轮训执行的线程在io线程。在polling flow收集的时候使用默认的ui线程。这样保证了flowOn方法前的部分执行在io线程,flowOn方法后的部分执行在ui线程,进而达到线程切换的目的。这里使用retryWhen方法处理轮训异常,当有异常发生时,延时polling时间间隔后进行重试。

? ? ? ? 我们调用了lifecycleScope.launchWhenResumed方法收集flow,这样保证了polling flow只在画面被唤醒的状态下被收集。launchWhenResumed方法是通过切断消息分发来达到挂起的目的,如果在launchWhenResumed方法中又启动了协程进行轮训操作,那么阻止消息分发并不能停止launchWhenResumed方法内部启动协程的轮训操作。在lifecycle-runtime-ktx 2.4.0版本中引入了lifecycle.repeatOnLifecycle方法,这个方法可以根据生命周期进行取消和重启。由于它实现的是协程取消和协程重启,所以在这个方法内部启动的协程也会被取消和重启,进而解决了画面挂起时子协程不被取消而引起的泄露问题。

总结

  1. flow可以充分利用协程的结构化异步的优势实现异步轮训,避免使用启动线程方式进行轮训操作。
  2. 线程轮训的方式中延时操作阻塞了线程,flow中的延时操作挂起协程但不阻塞线程,所以flow节省了线程资源,协程挂起时线程还可以处理其他的任务。
  3. 创建线程的代价比启动协程的代价更高,并且线程的管理更加麻烦,我们要时刻关心线程状态,控制线程的启动与停止。但是协程依仗结构化异步的特点,用户不需要投入过多的经历管理协程的启动和停止。
  4. 使用flow可以通过声明的方式定义polling处理流程,代码逻辑简单清晰。比如通过flow的retryWhen声明重试处理,通过catch捕获polling异常,通过flowOn方法进行线程切换等。

我的公众号已经开通,公众号会同步发布。
欢迎关注我的公众号

?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-03 17:12:26  更:2021-10-03 17:13:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:23:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码