Kotlin has been around for many years, and Google has made it the preferred development language for Android, so it’s worth investigating. The basic syntax will not be retold, but one of kotlin’s core content: Coroutine.

The related concepts and APIS can be found directly on the official website, but kotlin Chinese is not recommended. The translation of the article is a little hard, which makes it easier to confuse.

This article does not cover the basics (I do not know the basics…). , suitable for students who like to use doctrine

As a lightweight thread, coroutine is a scheduler based on threads, which saves resources but does not mean that it does not consume resources. In the environment where system resources are relatively precious like Android, it is also necessary to recycle resources in time when asynchronous and tedious calls or threads are switched.

Add a dependency configuration

Coroutine is an official 1.0 release in Kotlin 1.3, so it is recommended to use this version first.

Gradle configuration in project root:

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"}}Copy the code

Gradle configuration in the main Module

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 is no longer required in the current version'enable'}}Copy the code

If your other modules also use KT code, you need to add the above dependencies as well, otherwise you may get class missing or symbol related compilation errors


Replace RxJava and upgrade Retrofit

Typical projects refer to a combination of Retrofit + RxJava for network requests, or you encapsulate a separate layer of RxJava for asynchronous operations, switching threads to handle your business

RxJava has a lot of operators (100 +) that are hard to get started with, but it’s easy to use. One chain takes care of all of your business, and it’s not great if there’s a web request for an RX-enabled library like Retrofit.

A common problem with functional development, though, is that this chain creates a lot of objects, and you have to think about memory jitter, but even with thread pools, it can be expensive.

While RxJava can be called chaining, it is still based on callbacks, whereas coroutines completely write asynchronous code synchronously.

Look at the way Retrofit was written in the past (easy)

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("The data/benefits / {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() }) } }Copy the code

RxJava’s call chain uses RxLife to bind the life cycle (or use Uber’s AutoDispose), common code.




For RxJava you may also package a widget (mainly for lifecycle and recycling issues)

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); }}Copy the code


Now, what do you do with coroutines, assuming you now know how to start a coroutine

GlobalScope.launch { }
        
GlobalScope.async {}        

Copy the code

Now that you know the difference between launch and async (with different return values), and that you need to choose between one return value and multiple return values (async and reduce), how do you use it in Android




Three, encapsulation

The parent coroutine controls the execution of the child coroutine. To cancel the parent coroutine, you close all the tasks you open in the parent coroutine. So in the base class you need to declare a global coroutine context to make sure that all the coroutines you open are in that context. Cancelling the global context under onDestroy accomplishes your purpose

abstract class AbstractFragment : RxFragment() , CoroutineScope by MainScope() {

    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}
Copy the code

You can encapsulate a tool to use coroutines in the same way you encapsulate RxJava, while following some principles:

1 the scheduler

The coroutine you turn on needs to have a scheduler, just as RxJava freely switches threads, but the Main scheduler is best used to specify Main to avoid unnecessary trouble.

For example, if you use RxJava+ Autodipose to bind the lifecycle, a crash will occur when the child thread starts a new thread using RxJava, scheduling the UI thread back to refresh the view

There is a strong hierarchical relationship between parent coroutines and child coroutines

Cancel A Job throws a CancellationException, which the developer does not need to manage, but which is used to cancel the parent Coroutine if a non-CancellationException occurs inside the Coroutine. To ensure a stable Coroutine hierarchy, this behavior cannot be modified. Then, when all child coroutines terminate, the parent Coroutine receives the exception that was thrown.


Below is a simple encapsulation case of a coroutine

    fun <T> executeRequest(context : CoroutineContext, request: suspend() -> T? , onSuccess: (T) -> Unit = {}, onFail: (Throwable) -> Unit = {}): Job {returnCoroutineScope(Dispatchers.Main).launch(context) { try { val res: T? = withContext(Dispatchers.IO) { request() } res? .let { onSuccess(it) } } catch (e: Exception) { e.printStackTrace() onFail(e) } } }Copy the code

Context is passed in or out depending on the business

Then call it directly in your act or Fragment

class MyFragment : BaseFragment {
    
    onAcitvityCreate(){executeRequest<Body>(context, request = {// asynchronous task}, onSuccess = {}, onFail = {})}}Copy the code






Retrofit changes are even simpler. Coroutines are already supported in version 2.6, and there are many online introductions

interface GankMeiziAPI {
    @GET("The data/benefits / {number} / {page}")
    suspend fun getGankMeizi(@Path("number") number: Int, @Path("page") page: Int): GankMeiziResult
}
Copy the code

To change the Observable return value to the bean you want, add the suspend flag. Here’s an example:

returnexecuteRequest<GankMeiziResult>( context, request = { RetrofitHelper.getGankMeiziApi().getGankMeizi(pageNum, page) }, onSuccess = { mvpView? .showSuccess(it.results) }, onFail = { mvpView? .showError() } )Copy the code


Four, confused

Most of the posts on the Internet are written with the idea of demo, which will definitely be confused for commercial use. If you don’t add ProGuard, you will die after confusion, so directly post it

Coroutine configuration

-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>;
}
Copy the code

If your Retrofit request results in an NPE after release packaging, check to see if your bean has added an unconfused-configuration




other

Study coroutine not long, there are mistakes or suggestions welcome to point out, common exchange

I also recommend Cloud’s blog at Qianfeng, which is the friendliest series of Coroutine articles I have read so far