背景
在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办法,这个办法能够依据生命周期进行勾销和重启。因为它实现的是协程勾销和协程重启,所以在这个办法外部启动的协程也会被勾销和重启,进而解决了画面挂起时子协程不被勾销而引起的泄露问题。
总结
flow能够充分利用协程的结构化异步的劣势实现异步轮训,防止应用启动线程形式进行轮训操作。
线程轮训的形式中延时操作阻塞了线程,flow中的延时操作挂起协程但不阻塞线程,所以flow节俭了线程资源,协程挂起时线程还能够解决其余的工作。
创立线程的代价比启动协程的代价更高,并且线程的治理更加麻烦,咱们要时刻关怀线程状态,控制线程的启动与进行。然而协程依仗结构化异步的特点,用户不须要投入过多的经验治理协程的启动和进行。
应用flow能够通过申明的形式定义polling解决流程,代码逻辑简略清晰。比方通过flow的retryWhen申明重试解决,通过catch捕捉polling异样,通过flowOn办法进行线程切换等。
我的公众号曾经开明,公众号会同步公布。
欢送关注我的公众号
————————————————
版权申明:本文为CSDN博主「mjlong123123」的原创文章,遵循CC 4.0 BY-SA版权协定,转载请附上原文出处链接及本申明。
原文链接:https://blog.csdn.net/mjlong1...