协程的魅力你感受到了吗?-传统异步任务和协程的使用对比

示例1 登录并返回用户信息

传统异步方式

使用Retrofit+Handler

1、引入Retrofit依赖

// Retrofit库
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:retrofit-mock:2.7.2"
implementation "com.squareup.retrofit2:converter-gson:2.7.2"
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
implementation "com.squareup.retrofit2:converter-scalars:2.7.2"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.7.2"

2、定义接口

interface WanAndroidApi {
    @POST("/user/login")
    @FormUrlEncoded
    fun loginAction(@Field("username") username: String, @Field("password") password: String):
            Call<ResponseWrapper<LoginRegisterResponse>>
}

3、相应数据实体

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?
)

4、响应数据包装类

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

5、数据请求Client

class ApiClient {

    private object Holder {
        val INSTANCE = ApiClient()
    }

    companion object {
        val instance = Holder.INSTANCE
    }

    fun <T> instanceRetrofit(apiInterface: 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 = Retrofit.Builder().baseUrl("https://www.wanandroid.com")
            //请求方
            .client(mOkhttpClient)
            //响应方
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//RxJava处理
            .addConverterFactory(GsonConverterFactory.create())//Gson解析
            .build()

        return retrofit.create(apiInterface)
    }
}

6、view层请开启子线程发起请求,然后通过Handler切换到主线程更新UI

//第二步:用Handler接收请求结果并更新UI
private val mHandler = Handler(Looper.getMainLooper()) {
    val result = it.obj as ResponseWrapper<*>
    tv_response.text = result.data.toString()
    
    mProgressDialog?.dismiss()
    
    false
}

//第一步:开启异步线程,请求服务器
thread {
    val loginResult = ApiClient.instance.instanceRetrofit(WanAndroidApi::class.java)
        .loginAction("Derry-vip", "123456")
        
    val result = loginResult.execute().body()
    
    //切换到主线程更新UI,把请求结果发送给Handler
    val msg = mHandler.obtainMessage()
    msg.obj = result
    mHandler.sendMessage(msg)
}

携程方式

使用Retrofit+协程,数据实体、包装类,请求Client同上。

1、定义接口

interface WanAndroidApi {
    @POST("/user/login")
    @FormUrlEncoded
    suspend fun loginActionCoroutine(
        @Field("username") username: String,
        @Field("password") password: String
    ): ResponseWrapper<LoginRegisterResponse>
}

使用suspend关键字修饰,起到一个提示的作用,表示该函数会使用协程挂起,有一个代码颜色规则。用suspend修饰的方法中需要使用协程,比如withContext(),否则suspend关键字没有意义,并且会置灰。而一旦使用了协程,该方法就必须用suspend来修饰。

2、使用协程发起请求

//使用协程登录 同步代码实现异步效果
//GlobalScope.launch()默认为IO线程,需要用GlobalScope.launch(Dispatchers.Main)切换到主线程
GlobalScope.launch(Dispatchers.Main) {
    //UI线程
    val result = ApiClient.instance.instanceRetrofit(WanAndroidApi::class.java)
        .loginActionCoroutine("Derry-vip", "123456")//1、挂起出去执行异步操作 2、操作完成后恢复主线程
        
    //应为{}内为主线程,所以可以直接更新UI
    tv_response.text = result.data.toString()
    
    mProgressDialog?.dismiss()
}

注意: GlobalScope.launch()默认为异步(IO)线程,需要用GlobalScope.launch(Dispatchers.Main)切换到主线程,并且实际开发中很少直接用GlobalScope,因为它是作用于全局的,只有在进程被kill的时候才会销毁。实际开发可以使用viewModelScope等。

对比

1、使用传统的异步请求,代码量比较大,很繁琐,使用协程代码非常简洁。

2、使用传统的异步请,请求完数据再反过来发送给Handler去处理代码的逻辑无法按照人类正常的思维认知的顺序去写;而使用协程,用同步代码实现效果,所有逻辑都是从上到下很流畅,符合人类的认知顺序。

示例2 模拟按顺序调用3个接口并返回信息

传统异步方式

1、定义接口回调,回调请求结果

/**
 * 模拟请求结果回调
 */
interface ResponseCallBack {

    /**
     * 服务器请求成功
     * @param successInfo 成功回调信息
     */
    fun onSuccess(successInfo: String)

    /**
     * 服务器请求失败
     * @param errorInfo 失败回调信息
     */
    fun onError(errorInfo: String)

}

2、定义3个接口的请求方法

//第一步 请求用户数据
private fun requestUserInfo(responseCallBack: ResponseCallBack) {
    val requestSuccess = true

    //开启异步线程,加载服务器数据
    thread {
        Thread.sleep(2000)

        if (requestSuccess) {
            responseCallBack.onSuccess("请求用户数据 成功")
        } else {
            responseCallBack.onError("请求用户数据 失败")
        }

    }
}

//第二步 请求课程数据
private fun requestLessonInfo(responseCallBack: ResponseCallBack) {
    val requestSuccess = true

    thread {
        Thread.sleep(2000)

        if (requestSuccess) {
            responseCallBack.onSuccess("请求课程信息 成功")
        } else {
            responseCallBack.onError("请求课程数据 失败")
        }
    }
}

//第二步 请求课程详情数据
private fun requestLessonDetailInfo(responseCallBack: ResponseCallBack) {
    val requestSuccess = true

    thread {
        Thread.sleep(2000)

        if (requestSuccess) {
            responseCallBack.onSuccess("请求课程详情信息 成功")
        } else {
            responseCallBack.onError("请求课程详情数据 失败")
        }
    }
}

3、按顺序发起请求,并回调结果更新UI

private fun requestData() {
    mProgressDialog = ProgressDialog(this)
    mProgressDialog?.setTitle("loading...")
    mProgressDialog?.show()
    
    //请求用户信息
    requestUserInfo(object : ResponseCallBack {
        override fun onSuccess(successInfo: String) {
            //用户信息请求成功
            val handler = object : Handler(Looper.getMainLooper()) {
                
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)
                    
                    //更新UI
                    tv_response.text = successInfo
                    tv_response.setTextColor(Color.RED)
                    
                    //请求课程信息
                    requestLessonInfo(object : ResponseCallBack {
                        override fun onSuccess(successInfo: String) {
                            //课程信息请求成功
                            val handler = object : Handler(Looper.getMainLooper()) {
                                override fun handleMessage(msg: Message) {
                                    super.handleMessage(msg)
                                    
                                    //更新UI
                                    tv_response.text = successInfo
                                    tv_response.setTextColor(Color.BLUE)
                                    
                                    //请求课程详情信息
                                    requestLessonDetailInfo(object : ResponseCallBack {
                                        override fun onSuccess(successInfo: String) {
                                            //课程详情信息请求成功
                                            val handler =
                                            object : Handler(Looper.getMainLooper()) {
                                                override fun handleMessage(msg: Message) {
                                                    super.handleMessage(msg)
                                                    
                                                    //更新UI
                                                    tv_response.text = successInfo
                                                    tv_response.setTextColor(Color.GREEN)
                                                    
                                                    mProgressDialog?.dismiss()
                                                    
                                                }
                                            }
                                            handler.sendEmptyMessage(0)
                                        }
                                        
                                        override fun onError(errorInfo: String) {
                                            
                                        }
                                    })
                                }
                            }
                            handler.sendEmptyMessage(0)
                        }
                        
                        override fun onError(errorInfo: String) {
                            
                        }
                    })
                }
                
            }
            handler.sendEmptyMessage(0)
        }
        
        override fun onError(errorInfo: String) {
            
        }
    })
    }

携程方式

1、定义3个接口的请求方法

//第一步 请求用户数据
//suspend 就是一个提醒作用,提醒用户 当前的函数,是挂起函数,可能会执行异常操作
private suspend fun requestUserInfo(): String {
    val requestSuccess = true

    withContext(Dispatchers.IO) {
        delay(2000L)
    }

    return if (requestSuccess) "请求用户数据 成功" else "请求用户数据 失败"
}

//第二步 请求课程数据
private suspend fun requestLessonInfo(): String {
    val requestSuccess = true

    withContext(Dispatchers.IO) {
        delay(2000L)
    }

    return if (requestSuccess) "请求课程信息 成功" else "请求课程数据 失败"
}

//第二步 请求课程详情数据
private suspend fun requestLessonDetailInfo(): String {
    val requestSuccess = true

    withContext(Dispatchers.IO) {
        delay(2000L)
    }

    return if (requestSuccess) "请求课程详情信息 成功" else "请求课程详情数据 失败"
}

2、使用协程发起请求,并更新UI

private fun requestData() {
    val progressDialog = ProgressDialog(this)
    progressDialog.setTitle("loading...")
    progressDialog.show()
    
    //一般不用GlobalScope GlobalScope是全局的作用于,只有进程被kill的时候才会销毁
    GlobalScope.launch(Dispatchers.Main) {
        
        val result1 = requestUserInfo()
        tv_response.text = result1
        tv_response.setTextColor(Color.RED)
        
        val result2 = requestLessonInfo()
        tv_response.text = result2
        tv_response.setTextColor(Color.BLUE)
        
        val result3 = requestLessonDetailInfo()
        tv_response.text = result3
        tv_response.setTextColor(Color.GREEN)
        
        progressDialog.dismiss()
    }
}

对比

传统方式代码量很大,还需要进行大量的嵌套,代码阅读困难;携程方式代码极度简洁,很容易阅读。

关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 其实,协程在编程语言中并不是什么新鲜概念。像 go, python 也有协程的概念,只不过 API 不尽相同。 为...
    小牧扎特阅读 294评论 0 0
  • 在今年的三月份,我因为需要为项目搭建一个新的网络请求框架开始接触 Kotlin 协程。那时我司项目中同时存在着两种...
    业志陈阅读 1,001评论 0 5
  • 在今年的三月份,我因为需要为项目搭建一个新的网络请求框架开始接触 Kotlin 协程。那时我司项目中同时存在着两种...
    Android开发指南阅读 778评论 0 2
  • 协程属于Kotlin中非常有特色的一项技术,因为大部分编程语言中是没有协程这个概念的。 那么什么是...
    隨風cvil阅读 796评论 0 1
  • 什么是协程? 官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解...
    pureChild阅读 788评论 0 1