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的函數的時候最好仍是多點進去看看,看看到底會不會拋異常,免的搞出線上事故。