kotlin出來不少年,並被google被賦予Android首選開發語言的地位,仍是有必要研究一下的,基礎語法再也不復述了,只簡單聊聊kotlin中的核心內容之一:協程 Coroutine。java
相關概念和API能夠直接進入官網查詢, 不過不推薦kotlin中文網,文章翻譯的略顯生硬,看了更容易迷惑。android
本文不講基礎(基礎我也不怎麼清楚。。),適合喜歡拿來主義的同窗api
協程做爲輕量級的線程,是創建在線程之上的調度,比線程節省資源但不意味着不消耗資源,在Android這種系統資源相對珍貴的環境中,當異步繁瑣的調用或者切換線程,如何及時的回收也是必要的工做。bash
coroutine 是在kotlin 1.3中發佈的1.0正式版,因此建議優先使用這個版本。網絡
項目根目錄中gradle配置:app
buildscript {
ext.kotlin_version = '1.3.0'
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
複製代碼
主module中gradle配置異步
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
}
// 實驗性開啓協程的配置,目前版本中已經不須要了
kotlin{
experimental{
coroutines 'enable'
}
}
複製代碼
假如你的其餘module也使用kt代碼,也須要添加上述依賴,否則有可能會出現找不到class或符號相關編譯錯誤async
通常項目中會引用retrofit + RxJava的組合進行網絡請求,或者你單獨封裝了一層RxJava來進行異步操做、切換線程來處理你的業務maven
RxJava雖然操做符衆多(得有100個以上了吧),上手不易,但着實好用,一條鏈走下來搞定你的所有業務,若是有網絡請求配合上retrofit這種支持Rx的庫,不要太給力。ide
不過函數式開發有個通病,這條鏈子上會建立大量對象,回收內存抖動你不得不考慮,雖然使用了線程池,可開銷不容小覷。
RxJava 雖然能夠鏈式調用,但終究仍是基於回調形式,而協程徹底作到了同步方式寫異步代碼。
看下之前Retrofit的寫法(簡易)
object RetrofitHelper: Interceptor {
init {
initOkHttpClient();
}
private var mOkHttpClient: OkHttpClient? = null
private val BASE_GANK_URL = "http://gank.io/api/"
fun getRetroFitBuilder(url :String) : Retrofit {
return Retrofit.Builder()
.baseUrl(url)
.client(mOkHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun getGankMeiziApi(): GankMeiziAPI {
return getRetroFitBuilder(BASE_GANK_URL).create(GankMeiziAPI::class.java)
}
interface GankMeiziAPI {
@GET("data/福利/{number}/{page}")
abstract fun getGankMeizi(@Path("number") number: Int, @Path("page") page: Int): Observable<GankMeiziResult>
}
class GankMeiziPresenter : BaseMvpPresenter<IGankMeiziView>() {
fun getGankList(context: RxAppCompatActivity, pageNum: Int , page :Int){
RetrofitHelper.getGankMeiziApi()
.getGankMeizi(pageNum, page)
.subscribeOn(Schedulers.io())
.compose(context.bindUntilEvent(ActivityEvent.DESTROY))
.filter({ gankMeiziResult -> !gankMeiziResult.error })
.map({ gankMeiziResult -> gankMeiziResult.results })
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ gankMeiziInfos ->
mvpView?.showSuccess(gankMeiziInfos)
}, { throwable ->
mvpView?.showError()
})
}
}
複製代碼
RxJava 的調用鏈上使用RxLife來綁定生命週期(或者使用Uber的autodispose),很常見的代碼。
對於RxJava你還有可能會封裝一個小工具(主要是爲了生命週期和回收問題)
public class RxImpl {
public static <T> void exeOnLife(final AppCompatActivity mActivity , Observable<RespBody<XueError, T>> observable, final Accept acceptFun){
if (mActivity == null || ActivityHelper.activityIsDead(mActivity) || observable == null) return;
observable.as(AutoDispose.<RespBody<XueError,T>>autoDisposable(AndroidLifecycleScopeProvider.from(mActivity, Lifecycle.Event.ON_DESTROY)))
.subscribe(new Consumer<RespBody<XueError, T>>() {
@Override
public void accept(RespBody<XueError, T> respBody) throws Exception {
if (acceptFun == null) return;
acceptFun.accept(respBody);
}
});
}
public interface Accept <E extends XueError,T extends Object> {
void accept(RespBody<E, T> respBody);
}
}
複製代碼
如今呢,想用協程怎麼辦,假設你如今已經知道了如何啓動一個協程
GlobalScope.launch { }
GlobalScope.async {}
複製代碼
也知道了launch 和 async的區別(返回值不一樣),也知道了須要一個返回值和多個返回值的選擇(async 和 reduce),那在Android中如何使用呢
協程Job具備層級關係,父協程控制子協程的運行,取消父協程的運行,也就至關於關閉了全部你開在父協程裏的所有任務,因此在基類中你須要聲明一個全局的協程上下文,保證你開啓的協程都處於這個context環境中,在onDestroy下取消全局context,就達到了你的目的
abstract class AbstractFragment : RxFragment() , CoroutineScope by MainScope() {
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
複製代碼
你能夠像封裝RxJava同樣,封裝一個工具來使用協程,同時遵循一些原則:
你開啓的協程須要有調度器,就像RxJava自由切換線程同樣,但主線程調度器最好指定Main來避免沒必要要的麻煩。
舉個例子,你使用RxJava+autodipose來綁定 生命週期,但你在子線程用RxJava又開啓了一個新線程,調度回UI線程刷新view的時候會發生崩潰
cancel一個Job 會拋出 CancellationException ,但這個異常不須要開發人員管理,但若是協程內部發生了非 CancellationException ,則會使用這個異常來取消父Coroutine。爲了保證穩定的Coroutine層級關係,這種行爲不能被修改。 而後當全部的子Coroutine都終止後,父Coroutine會收到原來拋出的異常信息。
下邊是個協程的簡易封裝案例
fun <T> executeRequest(context : CoroutineContext, request: suspend () -> T?, onSuccess: (T) -> Unit = {}, onFail: (Throwable) -> Unit = {}): Job {
return CoroutineScope(Dispatchers.Main).launch(context) {
try {
val res: T? = withContext(Dispatchers.IO) { request() }
res?.let {
onSuccess(it)
}
} catch (e: Exception) {
e.printStackTrace()
onFail(e)
}
}
}
複製代碼
context 視業務狀況而定是否傳入
而後在你的act或Fragment中直接調用
class MyFragment : BaseFragment {
onAcitvityCreate(){
executeRequest<Body>(
context,
request = {
// 異步任務
},
onSuccess = {
},
onFail = {
}
)
}
}
複製代碼
對於Retrofit修改更爲簡單,2.6版本已經支持了協程,網上有不少介紹
interface GankMeiziAPI {
@GET("data/福利/{number}/{page}")
suspend fun getGankMeizi(@Path("number") number: Int, @Path("page") page: Int): GankMeiziResult
}
複製代碼
將Observable返回值修改成你須要的bean, 加上suspend標記便可, 下邊是示例代碼:
return executeRequest<GankMeiziResult>(
context,
request = {
RetrofitHelper.getGankMeiziApi().getGankMeizi(pageNum, page)
},
onSuccess = {
mvpView?.showSuccess(it.results)
},
onFail = {
mvpView?.showError()
}
)
複製代碼
網上的帖子大多以demo的思路來寫,商用確定要混淆了,不添加proguard你混淆後就死翹翹了,因此直接貼上來
coroutine的配置
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
複製代碼
假如release打包後你的Retrofit 請求結果發生了npe,檢查下你的bean是否添加了不混淆配置
研究協程沒多久,有錯誤或建議歡迎指出,共同交流
另外推薦雲在千峯的博客 ,是我目前看過的最友好的coroutine系列文章