一、如何使用协程

1.1 添加依赖

implementation

'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'

implementation

'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'

1.2 使用协程Coroutine

在kotlinx.coroutines包中,你可以使用launch或async启动一个协程。从概念上讲,async就像launch一样,它启动一个单独的协程,协程相当于一个轻量级的线程,与其他所有的协同程序同时工作。

async和launch不同的地方在于,launch返回一个Job并且不携带任何结果值,而async返回Deffered。

Deffered表示一个轻量级的非阻塞未来,表示稍后提供结果的承诺。我们可以使用await()方法获取一个deffered的返回结果。Deffered本质上也是Job,因此可以在需要的时候取消它。

如果在launch中的代码因为异常而终止,那么它会被是为线程中未捕获异常而导致应用崩溃。异步代码中未捕获异常存储在生成的Deffered中,并且不会在其他任何地方传递,除非经过处理,否则它会被静默删除。

协程分发

在Android中,我们常用的又两个分发器dispatcher:

uiDispatcher:将执行分发到Android主UI线程(用于父协程)

bgDispatcher:在后台线程中调度执行(用于子协程)

// dispatches execution into Android main thread

val uiDispatcher: CoroutineDispatcher = Dispatcher.Main

// represent a pool of shared thread as coroutine dispatcher

val bgDispatcher: CoroutineDispatcher = Dispatcher.IO

协程作用域

使用协程需要提供协程对应的作用域CoroutineScope或使用GlobalScope

// GlobalScope示例

class MainFragment : Fragment(){

fun loadData() = GlobalScope.launch{...}

}

//CoroutineScope示例

class MainFragment : Fragment(){

val uiScope = CoroutineScope(Dispatchers.Main)

fun loadData() = uiScope.launch{...}

}

//Fragment实现CoroutineScope示例

class MainFragment : Fragment(),CoroutineScope{

override val coroutineContext: CoroutineContext

get() = Dispatcher.Main

fun loadData() = launch {...}

}

lauch+async(执行任务)

父协程通过Main Dispatcher调用的launch方法启动。

子协程通过IO Dispatcher调用async方法启动。

Note:父协程会一直等待它的子协程完成

Note:协程如果发生未捕获异常,程序会崩溃

val uiScope = CoroutineScope(Dispatchers.Main)

fun loadData() = uiScope.launch {

view.showLoading() //ui thread

val task = async(bgDispatcher){ //background thread

// your blocking call

}

val result = task.await()

view.showDta()

}

lauch+withContext(执行任务)

使用上一个例子中的方法,我们可以正常的运行。但我们浪费了启用第二个后台任务协程的资源。

如果我们只启用一个协程,可以使用withContext来优化我们的代码。

后台任务通过带有IO Dispatcher的withContext函数执行。

val uiScope = CoroutineScope(Dispatcher.Main)

fun loadData() = uiScope.launch {

view.showLoading() //ui thread

val result = withContext(bgDispatcher){

// your blocking call

}

view.showData(result) // ui thread

}

launch+ withContext(按顺序执行两个任务)

val uiScope = CoroutineScope(Dispatchers.Main)

fun loadData() = uiScope.launch {

view.showLoading() // ui thread

val result1 = withContext(bgDispatcher){

// your blocking call

}

val result2 = withContext(bgDispatcher){

//your blocking call

}

val result = result1 + result2

vuew,showData(result) //ui thread

}

launch+async+async(并行执行两个任务)

val uiScope = CoroutineScope(Dispatcher.Main)

fun loadData() = uiScope.launch {

view.showLoading() // ui thread

val task1 = async(bgDispatcher){

//your blocking call

}

val task2 = async(bgDispatcher){

//your blocking call

}

val result = task1.await() + task2.await()

view.showData() // ui thread

}

二、如何使用协程的timeout

如果我们想为一个协程任务设置超时,我们可以使用withTimeoutOrNull()方法,如果超时就返回null。

val uiScope = CoroutineScope(Dispatchers.Main)

fun loadData() = uiScope.launch {

view.showLoading() // ui thread

val task = async(bgDispatcher){

//your blocking call

}

// suspend until task is finished or return null in 2s

val result = withTimeoutOrNull(2000) { task.await() }

view.showData(result) // ui thread

}

三、如何取消一个协程

3.1 job

loadData()方法返回一个Job对象,Job对象是可以被取消的。当父协程被取消的时候,它的所有子协程都会被结束。当stopPresenting()方法被调用,view.showData()肯定不会被调用。

val uiScope = CoroutineScope(Dispatchers.Main)

val job: Job? = null

fun startPresenting(){

job = loadData()

}

fun stopPresenting(){

job?.cancel()

}

fun loadData() = uiScope.launch {

view.showLoading() // ui thread

val result = withContext(bgDispatcher){

// your blocking call

}

view.showData(result) //ui thread

}

3.2 parent job

取消协程的另一种方法是创建SupervisorJob对象,并通过重载+运算符在作用域构造函数中指定它。

var job = SipervisorJob()

val uiScope = CoroutineScope(Dispatchers.Main + job)

fun startPresenting(){

loadData()

}

fun stopPresenting(){

scope.coroutineContext.cancelChildren()

}

fun loadData() = uiScope.launch {

view.showLoading()

val result = withContext(bgDispatcher) {

// your blocking call

}

view.showData(result)

}

3.3 自定义具有生命周期感知的协程作用域

class MainScope : CoroutineScope, LifecycleObsever {

private val job = SupervisorJob()

override val coroutineContext: CoroutineContext

get() = job + Dispatchers.Main

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)

fun destory() = coroutineContext.cancelChildren()

}

//使用

class MainFragment : Fragment(){

private val uiScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?){

super.onCreate(savedInstanceState)

lifecycle.addObserver(mainScope)

}

private fun loadData() = uiScope.launch {

val result = withContext(bgDispatcher) {

// your blocking call

}

}

}

下面,举一个在ViewModel中使用生命周期感知的协程。

open class ScopedViewModel : ViewModel(){

private val job = SupervisorJob()

protected val uiScope = CoroutineScope(Dispathcers.Main + job)

override fun onCleared(){

super.onCleared()

uiScope.coroutineContext.cancelChildren()

}

}

//使用

class MyViewModel : ScopedViewModel(){

private fun loadData() = uiScope.launch {

val result = withContext(bgDispatcher) {

// your blocking call

}

}

}

四、如何处理协程中的异常

4.1 try-catch

我们可以使用try-catcher捕获并处理异常

private fun loadData() = GlobalScope.launch(uiDispatcher) {

view.showLoading()

try {

val result = withContext(bgDispatcher) { dataProvider.loadData() }

view.showData(result)

} catch(e: Exception){

e.printStackTrace()

}

}

为了避免在Presenter中使用try-catch,最好在dataProvider.loadData()函数中处理异常并使其返回通用Result类。

data class Result(val success: T? = null,

val error: Throwable? = null)

private fun loadData() = launch(uiContext){

view.showLoading()

val task = async(bgContext) { dataProvider.loadData("Task") }

val result: Result = task.await()

if(result.success != null){

view.showData(result.success)

} else if(result.error != null){

result.error.printStackTrace()

}

}

4.2 async parent

使用async启动父协程来忽视异常。

private fun loadData() = GlobalScope.async(uiDispatcher) {

view.showLoading()

val result = withContext(bgDispatcher) { dataProvider.loadData() }

view.showData(result)

}

使用这种方法, 异常会被保存在job对象中。我们可以使用invokeOnCompletion()方法来取回它。

var job: Job? = null

fun startPresenting() {

job = loadData()

job?.invokeOnCompletion { it: Throwable? ->

it?.printStackTrace()

job?.getCompletionException()?.printStackTrace()

}

4.3 launch + coroutine exception handler

你可以将CoroutineExceptionHandler添加到父协同程序上下文以捕获异常并处理它们。

val exceptionHandler: CoroutineContext = CoroutineExceptionHandler {

-, throwable->

view.showData(throwable.message)

job = Job()

}

private fun loadData() = GlobalScope.async(uiDispatcher + exceptionHandler){

view.showLoading()

val result = withContext(bgDispatcher) { dataProvider.loadData() }

view.showData(result) //如果发生异常就不会被调用

}

五、如何测试协程

启动一个协程需要你指定一个CoroutineDispatcher。

class MainPresenter(val view: MainView,

val dataProvider: DataProviderAPI) {

private fun loadData() = GlobalScope.launch(Dispacthers.Main){

view.showLoading()

val result = withContetx(Dispatchers.IO) { dataProvider.loadData() }

view.showData(result)

}

}

如果你想为上面的MainPresenter编写一个单元测试,你可能需要指定一个协程context用于ui和background执行。

可能最简单的方法是向MainPresenter构造函数添加两个参数:uiDispatcher,默认值为Main,ioContext,默认值为IO。

class MainPresnter(val view: MainView,

val dataProvider: DataProviderAPI,

val uiDispatcher: CoroutineDispatcher = UI,

val ioDispatcher: CoroutineDispatcher = IO){

private fun loadData() = GlobalScope.launch(uiDispatcher) {

view.showLoading()

val result = withContext(ioDispatcher) { dataProvider.loadData() }

view.showData(result)

}

}

现在,您可以通过提供Unconfined来轻松测试您的MainPresenter类,它只会在当前线程上执行代码。

@Test

fun startPresenting(){

//given

val view = mock(MainView::class.java)

val dataProvider = mock(DataProviderAPI::class.java)

val presenter = MainPresenter(view,

dataProvider,

Dispatcher.Unconfined,

Dispacther.Unconfined)

//when

presenter.startPresenting()

//then

}

六、如何实现协程线程日志

要了解哪个协同程序执行当前工作,可以通过System.setProperty打开调试工具并通过Thread.currentThread().name来记录线程名称。

//调式模式

System.setProperty("kotlinx.coroutines.debug", if(BuildConfig.DEBUG) "on" else "off")

launch(UI) {

log("Data loading started")

val task1 = async { log("Hello") }

val task2 = async { log("World") }

val result = task1.await() + task2.await()

log("Data loading completed: $result")

}

fun log(msg: String){

Log.d(TAG, "[${Thread.currentThread().name}] $msg")

}

android java协程,Android协程——入门相关推荐

  1. Android Java层和Native层通信入门指南开篇

        Android Java层和Native层通信入门指南开篇 引言    做Android平台系统开发的小伙伴,应该经常会遇到要打通Android Framework层和C/C++层通信的通道问 ...

  2. android java 调用js,Android中Java和JavaScript交互实例

    Android提供了一个很强大的WebView控件用来处理Web网页,而在网页中,JavaScript又是一个很举足轻重的脚本.本文将介绍如何实现Java代码和Javascript代码的相互调用. 如 ...

  3. android java 指针,opencv android:向我的代码中添加cascade分类器后出现空指针异常

    我在casecadeclassifier.java类中收到空指针异常 在这里: Mat objects_mat = objects; detectMultiScale_4(nativeObj, ima ...

  4. android java静态库,Android make 中变量记录

    转换mk文件到bp文件 $ out/soong/host/linux-x86/bin/androidmk Android.mk > Android.bp 编译不同类型的模块 编译成 Native ...

  5. android java 指针异常处理,Android程序员日常开发中异常总结

    CaptainAndroid.png Java异常 平时开发中遇到的java异常很多,因为引起原因一目了然,当然也有不好解决的,比如一个简单的空指针异常你可能始终无法找到其为空的原因,甚至使用前还做了 ...

  6. android java kindle_Kindle和Android开发的比较:Java实现

    在<Kindle和Android开发的比较:硬件>中,我们已经为您介绍了Kindle硬件限制以及Android硬件潜力方面的内容.下面将继续为您介绍. Kindle java实现 为有限资 ...

  7. android java kindle_Kindle和Android开发的比较(2)

    Kindle Java实现 为有限资源设备定义一个Java子集的尝试有着长久而复杂的历史.Java微型版本(Java ME)的有些版本是被嵌入在上百万的手机和嵌入式处理器中.在Kindle中被用作出发 ...

  8. android java 圆角_java – Android:给一个webview圆角?

    我试图给我的webView圆角. 这是我的代码: rounded_webview.xml: android:shape="rectangle" android:padding=&q ...

  9. android java广播,[原]Android应用程序发送广播(sendBroadcast)的过程分析

    前面我们分析了Android应用程序注册广播接收器的过程,这个过程只完成了万里长征的第一步,接下来它还要等待ActivityManagerService将广播分发过来.ActivityManagerS ...

  10. android java 指针异常处理,Android自定义抛出异常的方法详解

    前言 在android开发过程中,我们经常遇到异常的问题,崩溃抛出异常的时候,是非常令人烦闷的.但是异常有一个好处,使得app能在编译的时候给我们提供一些bug信息,有时可能比较模糊,有时可能很精准, ...

最新文章

  1. 从头到尾使用Geth的说明-3-geth参数说明和环境配置
  2. dbgridview内操作粘贴,复制,等量复制,增量复制
  3. 算法竞赛入门经典(第二版) | 例题5-4 反片语 (map+标准化)(UVa156,Ananagrams)
  4. 热敏电阻温度特性曲线_热敏电阻与体温计的应用关系
  5. 面向对象设计原则之3-里氏替换原则
  6. eclipse开发java项目_用eclipse 开发java 项目
  7. Winfrom开发之动态生成TreeView树形菜单
  8. 工业和信息化部教育考试中心职业技术证书有必要考吗?
  9. 如何编辑SDE数据(转自ESRI中国社区)
  10. 2021 浏览器edge改 ie11 模式
  11. win10 登录显示0x800704cf错误代码
  12. 代码覆盖率工具lcov
  13. Java的内心世界和外部世界协调的统一
  14. MySQL:让表的时间字段在insert和update时自动更新
  15. Actin机器人控制软件,专注于机器人路径规划
  16. 华为智慧屏x1是鸿蒙系统吗,荣耀智慧屏x1和华为智慧屏s55有什么区别?哪个值得买...
  17. 实战 | OpenCV如何将不同轮廓合并成一个轮廓(附Python / C++源码)
  18. ansible主机清单配置详解
  19. ssl证书购买后的认证签发过程
  20. Part 13 (1) Fourier级数基本概念与应用

热门文章

  1. 电子画册宣传册制作系统源码 含搭建教程 多行业模板任意调用
  2. 重磅!9个中文免费电子书网站合集来了
  3. 【最简单解决办法】:module ‘tensorflow.compat.v1‘ has no attribute ‘contrib‘
  4. linux无盘常用查看硬件命令,网众LINUX无盘一些常用的命令
  5. text-indent首行缩进
  6. bzoj 4872 [Shoi2017]分手是祝愿
  7. 【观察】东风装备:数字化需“从面到点”,选对伙伴“事半功倍”
  8. 如何穷养儿子,富养女儿
  9. HTTPS与P=NP问题卍解(演讲)
  10. 解决Ubuntu下笔记本摄像头灯亮却黑屏问题