示例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>
}
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(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 instanceRetrofit(apiInterface: Class): 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
}
使用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】,及时获取更多最新技术干货。