kotlin 协程 lanch 详解

  • 前言
  • 一、协程是什么?
  • 二、传统方式完成异步网络加载和协程的对比
  • 2.1:传统方式完成异步任务网络加载
  • 2.2:协程方式完成异步任务网络加载
  • 2.3:传统方式完成三层回调
  • 2.4:协程方式解决三层回调带
  • 总结



前言

使用纯代码 加 注释的方式,可以更快的理解源码
如果你喜欢,请点个赞,后期会不断的深入讲解


一、协程是什么?

1.大部分开发者们把协程比喻成:线程的封装框架,从宏观角度看,这有一定道理
2.协程有点像轻量级的线程
3.从包含关系上看,协程跟线程的关系,有点像“线程与进程的关系”,毕竟,协程不可能脱离线程运行
4.协程虽然不能脱离线程而运行,但可以在不同的线程之间切换

二、传统方式完成异步网络加载和协程的对比

2.1:传统方式完成异步任务网络加载

接下来通过一个接口的模拟登录,来实现一下传统方式的步任务网络加载登录场景

代码如下:LoginRegisterResponseWrapper

data class LoginRegisterResponseWrapper<T>(val data: T, val errorCode: Int, val errorMsg: String)

代码如下:LoginRegisterResponse

data class LoginRegisterResponse(
    val admin: Boolean,
    val chapterTops: List<*>,
    val collectIds: List<*>,
    val email: String?,
    val icon: String?,
    val id: String?,
    val nickname: String?,
    val password: String?,
    val publicName: String?,
    val token: String?,
    val type: Int,
    val username: String?
)

定义一个接口 代码如下:WanAndroidAPI

interface WanAndroidAPI {

//    TODO 下面是传统方式API


    /** https://www.wanandroid.com/blog/show/2
     * 登录API
     * username=Derry-vip&password=123456
     */

    @POST("/user/login")
    @FormUrlEncoded
    fun loginAction(
        @Field("username") username: String,
        @Field("password") password: String
    )
            : Call<LoginRegisterResponseWrapper<LoginRegisterResponse>>   // 返回值
}

接口的实现 代码如下:APIClient

class APIClient {
    /**
     * 单例
     */
    private object Holder {
        val INSTANCE = APIClient()
    }

    companion object {    // 派生
        val instance = Holder.INSTANCE
    }

    fun <T> instanceRetrofit(apiInstance: Class<T>): T {
//       OkHttpClient 请求服务器
        val mOkHttpClient = OkHttpClient().newBuilder().apply {
            readTimeout(10000, TimeUnit.SECONDS)     //添加超时时间
            connectTimeout(10000, TimeUnit.SECONDS)   //添加连接超时时间
            writeTimeout(10000, TimeUnit.SECONDS)    // 添加写入超时时间
        }.build()

        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com")
            // 请求方  ←
            .client(mOkHttpClient)
            // 响应方  →
            // Response的事情  回来
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())     // RxJava来处理
            .addConverterFactory(GsonConverterFactory.create())            // Gson 来解析 --- JavaBean
            .build()

        return retrofit.create(apiInstance)
    }

}

XML 代码如下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.MainActivity">

    <TextView
        android:id="@+id/textview"
        android:text="启动线程"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/button"
        android:onClick="startRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="网络请求"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="OnClick" />

</androidx.constraintlayout.widget.ConstraintLayout>

具体功能实现,直接看代码吧,注释比较多
代码如下: MainActivity

class MainActivity : AppCompatActivity() {

   private var mProgressDialog: ProgressDialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun startRequest(view: View){

        mProgressDialog = ProgressDialog(this)
        mProgressDialog?.setTitle("请求服务器中...")
        mProgressDialog?.show()

//        TODO 第一步:异步线程开启,请求服务器
        object: Thread(){
            override fun run() {
                super.run()

                Thread.sleep(2000)   // 模拟一个两秒的加载时间
                val loginResult = APIClient.instance.instanceRetrofit(WanAndroidAPI::class.java)
                    .loginAction("Derry-vip", "123456")
                val result: LoginRegisterResponseWrapper<LoginRegisterResponse>? = loginResult.execute().body()

//                切换到主线程, 更新UI 把最终的javaBean 发送给Handler
                val msg = mHandler.obtainMessage()
                msg.obj = result
                mHandler.sendMessage(msg)
            }
        }.start()
    }

    //    TODO 第二步:主线程更新UI
    val mHandler = Handler(Looper.getMainLooper()){

//    as 类型转换
        val result = it.obj as LoginRegisterResponseWrapper<LoginRegisterResponse>
        textview.text = result.data.toString()   // 更新控件 UI

        mProgressDialog?.hide()

        false
    }
}

上面的代码实现,是传统完成的异步加载操作。需要先开启一个异步线程,然后再把登录成功的数据,再交给主线线程去做UI的更新操作。

2.2:协程方式完成异步任务网络加载

再来实现一个协程的

修改 WanAndroidAPI 里面的接口,添加 suspend 关键字,代码如下:

@POST("/user/login")
    @FormUrlEncoded
    suspend fun loginActionCoroutine(
        @Field("username") username: String,
        @Field("password") password: String
    )
            : LoginRegisterResponseWrapper<LoginRegisterResponse>  // 返回值

修改 MainActivity 中的代码,代码如下:

class MainActivity1 : AppCompatActivity() {

    private var mProgressDialog: ProgressDialog? = null
    private val main = MainScope()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun startRequest(view: View) {

        mProgressDialog = ProgressDialog(this)
        mProgressDialog?.setTitle("请求服务器中...")
        mProgressDialog?.show()

//       launch 在Android 中使用的时候,它默认的是 IO 线程,所以需要修改成Main 线程 (Dispatchers.Main )
        main.launch(Dispatchers.Main) {
            //                    1.挂起出去执行异步线程  2. 操作完成之后,恢复主线程
            val result = APIClient.instance.instanceRetrofit(WanAndroidAPI::class.java)
                .loginActionCoroutine("Derry-vip", "123456")

//            更新UI
            textview.text = result.data.toString()
            mProgressDialog?.hide()
        }

    }

    override fun onDestroy() {
        super.onDestroy()

        main.cancel()
    }
}

是不是简单了很多呢?不再需要创建异步线程,然后再交给主线程去修改UI,而是直接使用 launch 挂起出去执行异步操作,操作完成后直接恢复到主线程

这只是一个简单的一层回调的例子,可以想象一些,如果老板让我们实现一个三层回调,30层回调的时候,我们使用传统的处理方式会有多疼苦,接下来我们来实现一下。

2.3:传统方式完成三层回调

先写个回调接口 代码如下 ResponseCallback

/**
 * 模拟请求服务器后,相应结果信息
 */
interface ResponseCallback {

    /**
     * 请求服务器 加载成功
     */
    fun responseSuccess(serverResponseInfo: String)

    /**
     * 请求服务器 加载失败
     */
    fun responseError(serverResponseErrorMsg: String)

}

再写三个模拟数据请求的异步线程 代码如下:

/**
 * 第一层
 * 请求加载 [用户数据]
 *
 * responseCallback [回调给外界的接口]
 */
private fun requestLoadUser(responseCallback: ResponseCallback) {
    val isLoadSuccess = true        // 加载成功 或 失败的标记

    object : Thread() {
        override fun run() {
            super.run()
            try {
                sleep(3000L)     //  模拟请求 所造成的耗时
                if (isLoadSuccess) {
                    responseCallback.responseSuccess("加载到[用户数据]信息集")
                } else {
                    responseCallback.responseSuccess("加载[用户数据],加载失败,服务器宕机了")
                }
            } catch (e: InstantiationException) {
                e.printStackTrace()
            }
        }
    }.start()
}

/**
 * 第二层
 * 请求加载 [用户资产数据]
 *
 * responseCallback [回调给外界的接口]
 */
private fun requestLoadUserAssets(responseCallback: ResponseCallback) {
    val isLoadSuccess = true        // 加载成功 或 失败的标记

    object : Thread() {
        override fun run() {
            super.run()

            try {
                sleep(3000L)
                if (isLoadSuccess) {
                    responseCallback.responseSuccess("加载到[用户资产数据]信息集")

                } else {
                    responseCallback.responseError("加载[用户资产数据],加载失败,服务器宕机了")
                }
            } catch (e: InstantiationException) {
                e.printStackTrace()
            }
        }
    }.start()
}

/**
 * 第三层
 * 请求加载 [用户资产详情数据]
 *
 * responseCallback [回调给外界的接口]
 */
private fun requestLoadUserAssetsDetails(responseCallback: ResponseCallback) {
    val isLoadSuccess = true        // 加载成功 或 失败的标记

    object : Thread() {
        override fun run() {
            super.run()

            try {
                sleep(3000L)
                if (isLoadSuccess) {
                    responseCallback.responseSuccess("加载到[用户资产数据]信息集")

                } else {
                    responseCallback.responseError("加载[用户资产数据],加载失败,服务器宕机了")
                }
            } catch (e: InstantiationException) {
                e.printStackTrace()
            }
        }
    }.start()
}

具体功能实现,直接看代码吧,没什么好讲的
代码如下: MainActivity

class MainActivity2 : AppCompatActivity() {

    private var mProgressDialog: ProgressDialog? = null
    private val main = MainScope()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun startRequest(view: View) {

        mProgressDialog = ProgressDialog(this)
        mProgressDialog?.setTitle("请求服务器中...")
        mProgressDialog?.show()

//        TODO 先执行 异步请求 1
        requestLoadUser(object : ResponseCallback {
            override fun responseSuccess(serverResponseInfo: String) {
//                从异步 切换到主线程,更新UI
                val handler = object : Handler(Looper.getMainLooper()) {
                    override fun handleMessage(msg: Message) {
                        super.handleMessage(msg)

                        textview.text = serverResponseInfo
                        textview.setTextColor(Color.GRAY)

//                      TODO 先执行 异步请求 2
                        requestLoadUserAssets(object : ResponseCallback {
                            override fun responseSuccess(serverResponseInfo: String) {
                                val handler = object : Handler(Looper.getMainLooper()) {
                                    override fun handleMessage(msg: Message) {
                                        super.handleMessage(msg)

                                        textview.text = serverResponseInfo
                                        textview.setTextColor(Color.RED)

                                        //                      TODO 先执行 异步请求 3

                                        requestLoadUserAssetsDetails(object : ResponseCallback {
                                            override fun responseSuccess(serverResponseInfo: String) {
                                                val handler =
                                                    object : Handler(Looper.getMainLooper()) {
                                                        override fun handleMessage(msg: Message) {
                                                            super.handleMessage(msg)

                                                            textview.text = serverResponseInfo
                                                            textview.setTextColor(Color.BLUE)
                                                            mProgressDialog?.hide()

                                                        }
                                                    }

                                                handler.sendEmptyMessage(0)
                                            }

                                            override fun responseError(serverResponseErrorMsg: String) {
//                                TODO  失败的的逻辑就不写了
                                            }

                                        })
                                    }
                                }
                                handler.sendEmptyMessage(0)
                            }

                            override fun responseError(serverResponseErrorMsg: String) {
//                                TODO  失败的的逻辑就不写了
                            }

                        })
                    }
                }
                handler.sendEmptyMessage(0)
            }

            override fun responseError(serverResponseErrorMsg: String) {
//                TODO  失败的的逻辑就不写了
            }

        })

    }

}

无限嵌套,让我想起了flutter 中的嵌套陷阱。
再看一下 使用协程 实现的三层回调

2.4:协程方式解决三层回调带

先定义三个方法,分别开启一个异步线程。 代码如下:

/**
 * 请求加载[用户数据]
 *
 * suspend 就是一个提醒的作用,提醒用户,当前函数是挂起的函数,可能执行异常操作
 */
private suspend fun requestLoadUser(): String{
    val isLoadSuccess = true // 加载成功,和,加载失败,的标记
//    此协程能够保证在异步执行
    withContext(Dispatchers.IO){
        delay(3000)     //模拟请求服务器,造成的耗时
    }
    if (isLoadSuccess){
        return "加载到[用户数据]信息集"
    }else{
        return "加载[用户数据],加载失败,服务器宕机了"
    }
}


/**
 * 请求加载[用户资产数据]
 */
private suspend fun requestLoadUserAssets(): String{
    val isLoadSuccess = true // 加载成功,和,加载失败,的标记
//    此协程能够保证在异步执行
    withContext(Dispatchers.IO){
        delay(3000)     //模拟请求服务器,造成的耗时
    }
    if (isLoadSuccess){
        return "加载到[用户资产数据]信息集"
    }else{
        return "加载[用户资产数据],加载失败,服务器宕机了"
    }
}


/**
 * 请求加载[用户资产详情数据]
 */
private suspend fun requestLoadUserAssetsDetails(): String{
    val isLoadSuccess = true // 加载成功,和,加载失败,的标记
//    此协程能够保证在异步执行
    withContext(Dispatchers.IO){
        delay(3000)     //模拟请求服务器,造成的耗时
    }
    if (isLoadSuccess){
        return "加载到[用户资产详情数据]信息集"
    }else{
        return "加载[用户资产详情数据],加载失败,服务器宕机了"
    }
}

MainActivity 代码具体实现如下:

class MainActivity : AppCompatActivity() {

    private var mProgressDialog: ProgressDialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun startRequest(view: View) {

        mProgressDialog = ProgressDialog(this)
        mProgressDialog?.setTitle("请求服务器中...")
        mProgressDialog?.show()

//        Dispatchers.Main 包裹一层 Handler 切换主线程
        GlobalScope.launch(Dispatchers.Main) {

//            TODO 先执行 异步请求1
            var serverResponseInfo = requestLoadUser()
            textview.text = serverResponseInfo    // 更新UI
            textview.setTextColor(Color.YELLOW)   // 更新UI

//            TODO 更新UI完成后 异步请求2
            serverResponseInfo = requestLoadUserAssets()
            textview.text = serverResponseInfo    // 更新UI
            textview.setTextColor(Color.RED)   // 更新UI

//            TODO 更新UI完成后 异步请求3
            serverResponseInfo = requestLoadUserAssetsDetails()
            textview.text = serverResponseInfo    // 更新UI
            textview.setTextColor(Color.BLUE)   // 更新UI

            mProgressDialog?.hide()
        }

    }

}

看到这里,是不是发现很简单呢?


总结

难道协程很 ”高效“,”轻量“我们就要用协程吗?
答:其实协程的真正魅力是,最大程度简化异步并发任务,用同步代码写出异步效果

🤩
🎉android kotlin 判断为空_前端

👍 android kotlin 判断为空_android_02

🌟 android kotlin 判断为空_ui_03

✏️ android kotlin 判断为空_ui_04