kotlin-jvm 这套东西上的协程 其实就是个线程框架。 与go 语言那种高性能的协程是有本质不一样的,千万不要被迷惑了。除了阿里巴巴本身魔改的jvm之外,目前没有哪家jvm能够实现相似于go语言的那种协程能力。java
他最大的做用就是 若是你在某个方法前面加了suspend 那么这个方法的执行就会要求一个线程的执行上下文环境,不然会编译不经过。编程
看上图中的例子,被suspend 标识的io1 方法, 若是没有指定线程的执行环境 那么ide报红直接 编译不过, 可是若是你指定了他的线程上下文环境 则是能够编译经过的。bash
这里要注意的是 若是你用suspend方法标记的函数 函数体内部 并无使用协程关键字用来切线程的话, 那在编译的时候 suspend其实就没做用了,这里看图也能够知道 suspend在ide中显示成了灰色。网络
此外,所谓的kotlin协程的挂起 就是指切了个线程去干活,仅此而已。框架
其实就是方便给咱们切线程使用的,假设咱们有一个需求是简单的从本地sd卡上请求一个数据 而后显示在ui上jvm
纯java版本的ide
new Thread()
{
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mGoodsTitle.setText("adasdasda");
}
});
}
}.start();
复制代码
若是这个需求再复杂一点 ,好比须要你显示在ui上之后 再去网络请求个什么东西 而后接口回来再刷新ui。 这一套若是用原生的java来写,可想而知会很麻烦,并且代码会有不少回调。可读性也很差。函数
可是若是用Rxjava来写,则会简单不少性能
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
复制代码
咱们能够用 rxjava提供的这些操做符 来方便的切换咱们的线程,代码可读性会变的很是好。ui
有人要问了,那还须要kotlin的协程干啥?
kotlin的协程在处理相似代码的时候 会更加智能, 他的线程切走了之后 是能够自动切回来的 不须要你再手动的指定你的代码执行线程了。
举个例子:
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO) {
//do io sth
}
textview.text = " do ui sth"
withContext(Dispatchers.IO) {
//do io sth2
}
textview.text = " do ui sth2"
}
复制代码
这里就是典型的kotlin的 协程应用场景。 咱们先指定了一个线程执行上下文的环境 是main 也就是主线程。
而后咱们用协程的关键字 withContext 来切到io线程执行咱们的工做, 这个时候 后面的代码是不会走的。
必定会等到withContext里面的代码执行完毕之后, 才会走到 textview.text = " do ui sth" 这里。
而后后面咱们又执行了 withContext(Dispatchers.IO) 一次 ,这时 最后一行的textview.text 也是不会走的。
他必定会等到 上面的协程执行完毕之后 才会走。
因此这里你好好体会下 是否是会以为这种写法 比rxjava还要方便?毕竟线程切走之后 再切回来这个操做 kotlin协程帮咱们作好了,不再须要手动执行线程了 这个就是kotlin 协程的最大做用了。
其他的什么协程速度快啊之类的话 都是吹牛逼的。至少在kotlin-jvm上是不对的。
固然会,由于协程 前面咱们说过了,本质上就是个线程。 因此线程会致使activity内存泄露的场景 在协程中同样存在。 其实这里若是有rxjava 编程经验的同窗 应该大概知道怎么作了。这里rxjava的写法我就很少写了,写一下协程的吧。
首先定义一个scope
var scope= MainScope();
复制代码
而后稍微改变一下咱们的写法 注意此次不是global scope了 是咱们刚才定义的scope了
scope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO) {
//do io sth
}
textview.text = " do ui sth"
withContext(Dispatchers.IO) {
//do io sth2
}
textview.text = " do ui sth2"
}
复制代码
而后在这里 释放便可。
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
复制代码
跟Rxjava十分的像。就是换了个写法而已。 固然你若是还使用了lifecycle
那就更加简单了
lifecycleScope.launch { }
复制代码
这样释放的时候 咱们连cancel 均可以省略了, 所有交给谷歌提供的lifecycleScope就能够了。
最后提一下 coroutineScope 这个函数 也至关有用,能够限制咱们的协程执行环境 ,有兴趣的同窗能够自行搜索一下,这里再也不展开。
纯扯淡的说法,什么delay 不阻塞线程, sleep阻塞 线程 都是纯扯淡的说法,不要信。
本质上就是kotlin的delay函数 就是个协程,delay的本质就是切了个线程 而后在那个线程里面 sleep 了一段时间, 结束之后再切回来,仅此而已。
若是你本身读懂了我上面的文章 那么看到这个delay函数前面的suspend 其实你就知道是啥意思了。
确实很简单。能够看一下以前的写法:
这是java版本的:
咱们看下kotlin协程版本的:
可是这里要注意了,这里有个大坑,kotlin是一个 不强制异常处理检查的语言,因此这里的异常 是会抛出来的! 必定要注意,一旦网络请求有了诸如404 或者502的异常 你上面的代码就直接crush了。
其实想一想也能想明白 你看我这个简写的写法 甚至都没有地方能够处理onFailure 里面的流程,那万一发生了 onFailure里面的状况怎么办? 怎么把这些错误信息抛给你? 也只有异常了。
看下retrofit的 源码
一目了然。
因此使用retrofit+kotlin的时候 必定要谨记 主动捕获异常,一方面是为了程序不要crush 另一方面也是为了 处理程序中的异常状况
GlobalScope.launch(Dispatchers.Main) {
val reposItem = try {
retrofit.create(GitHubService::class.java)
.allRepositories2(page, "pushed:<" + Date().format("yyyy-MM-dd"), 10)
} catch (e: Exception) {
Log.v(
"wuyue",
"e:" + (e is HttpException) + " e:" + e.message + " e:" + (e is Throwable)
);
}
Log.v("wuyue", "reposItem2222222==$reposItem")
}
复制代码
对于java 来讲 ,若是你在方法内部 throw了 一个Exception 那你必须在方法签名的地方 主动用throws标记这个异常 而且在调用的时候 主动捕获,不然编译就会不经过。
看到没 这里是编译不过的。
改为这样就能够了。
调用的地方也是:
必须try catch 不然也是编译不过的。
可是要注意噢,若是是 RuntimeException 则不会出现上面的状况,若是你在方法体内部 throw 一个
RuntimeException ,那你方法签名的地方 不写throws 也是能够编译经过的。 调用的地方啥都不干 不写try catch 也没问题。 这里必定要注意。
最后就是坑爹的kotlin语言,无论你在方法里面 throw的 是 RuntimeException 仍是Exception 他都让你编译经过。。。
这就会致使 若是你调用一个函数,若是里面throw了一个异常,而后函数的注释或者文档又没告诉你这里可能会抛异常的话,那这个地方 可能就会发生可怕的事情了,对于客户端来讲就是crush。 目前我不知道kotlin为啥要这样设计,可是你们在调用kotlin的函数的时候最好仍是多点进去看看,看看到底会不会抛异常,免的搞出线上事故。