【Android】kotlin+协程+retrofit+mvvm 优雅地实现并封装网络请求

网络请求

在这里插入图片描述
网络请求是软件开发中必不可少的一个部分,通过客户端向服务器发送请求,获取服务器上的资源,这些资源可以是网页内容、数据、文件等。通常遵循HTTP或其他协议,http是超文本传输协议,被用于在web浏览器和网站服务器之间传递信息,基于TCP/IP通信协议。根据不同的需求和场景,网络请求可以采用不同的方法,如GET、POST、PUT、DELETE等。一个完整的网络请求分为三个部分:请求行、请求头、请求体。

在这里插入图片描述

GET和POST请求的区别如下:

1.get是接收数据,post是向服务器发送数据
2.get请求参数在地址栏显示,post请求参数在请求体显示,post安全性更高
3.get请求参数有长度限制,post请求参数没有长度限制
4.get请求体是没有内容,post请求体有内容

网络框架

安卓开发中有许多网络请求的框架,有原生的有第三方的,如Volley、HttpCliet AsyncHttpClient、Okhttp、OkhttpUtils、HttpUrlConnection、Retrofit。
在这里插入图片描述

本文将介绍Retrofit+协程更优雅地封装网络请求,kotlin的协程可以优化传统的Rxjava冗余,使得网络请求的实现代码更加简洁,可读性也更高。

之前写过一篇关于kotlin的语法知识,其中有关于这篇内容的协程、高阶函数的知识点:

Kotlin 核心语法详解(快速入门)
Kotlin 协程 高效并发详解( Kotlin Coroutine )

实现

1. 导入依赖

主要是网络请求retrofit和数据解析gson,版本号可灵活

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
 
dependencies {
    
    
    implementation 'androidx.core:core-ktx:1.1.0-alpha04'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
    implementation 'com.google.code.gson:gson:2.6.2'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
}

2. Retrofit公共请求类

class RetrofitClient {
    
    

	//单例模式
    companion object {
    
    
        fun getInstance() = SingletonHolder.INSTANCE

        private lateinit var retrofit: Retrofit
    }

    private object SingletonHolder {
    
    
        val INSTANCE = RetrofitClient()
    }

    init {
    
    
        retrofit = Retrofit.Builder()
                .client(getOkHttpClient())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(Constant.Config.BASEIP) //服务器地址,如http://xxxxx
                .build()
    }

    private fun getOkHttpClient(): OkHttpClient {
    
    
    	//配置请求的日志打印信息
        val httpLoggingInterceptor =
                HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
    
    
                    override fun log(message: String) {
    
    
                        if (BuildConfig.DEBUG) {
    
    
                            Log.i("--", "RetrofitFactory log: $message")
                        }
                    }
                })
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
                .connectTimeout(120, TimeUnit.SECONDS)
                .readTimeout(120, TimeUnit.SECONDS)
                .writeTimeout(120, TimeUnit.SECONDS)
                .connectionPool(ConnectionPool(8, 15, TimeUnit.SECONDS))
                .addInterceptor(httpLoggingInterceptor)
                .addInterceptor(object : Interceptor{
    
    
                override fun intercept(chain: Interceptor.Chain): Response {
    
    
                    val request = chain.request()
                    return xxx //配置网络拦截器,用来添加token,刷新token等前置操作,这里自定义
                }
            })
                .build()
    }


    /**
     * 创建一个 Service
     * @param service Class<T> 需要创建的 Service
     * @return T Service实例
     */
    fun <T> create(service: Class<T>): T = retrofit.create(service)

	//这个重载方法可以修改服务器地址
    fun <T> createBaseUrl(service: Class<T>,baseUrl:String): T =Retrofit.Builder()
        .client(getOkHttpClient())
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(baseUrl)
        .build()
        .create(service)

}

3. Api接口类

这里需要用到挂起函数,协程体调用外部的方法,它必须是suspend的,异步请求之后处理数据

interface Api {
    
    
 @GET("user/xxx")
 suspend fun getUserInfo():BaseResult<UserBean>
}
 
 
//通用数据结构,每个接口返回的数据结构大致需要这些字段
data class BaseResult <T>(
    val code :String,
    val success:Boolean,
    val message:String,
    val time:String,
    val data: T
 
)

4. ViewModel 拓展类

对VM扩展get和post方法,在请求接口时成功和失败的回调更加简洁。complete方法是最后执行的,可以进行加载框的关闭

/**
 * 请求网络数据并处理异常信息
 * @param api  数据请求体
 * @param success 请求成功
 * @param error  请求失败 默认弹出提示
 * @param complete  请求结束
 */
suspend fun <T> AndroidViewModel.get(
    api: suspend CoroutineScope.() -> Response<T>,
    success: suspend CoroutineScope.(T) -> Unit, //无函数体,必传
    error: suspend CoroutineScope.(Error) -> Unit = {
    
    }, //有函数体,可以不传
    complete: suspend CoroutineScope.() -> Unit = {
    
    }
) {
    
    
    get(
        api = api,
        success = {
    
    
            if(!it.success){
    
    
            	//错误的数据实体Error,只有code,message两个属性
                error(Error(0, it.message?:"获取数据失败"))
            }else if (it.result == null) {
    
    
                error(Response.Error(-2, "获取数据为空", null, null))
            } else {
    
    
                success(it.result)
            }
        },
        error = error,
        errorParser = {
    
    
            try {
    
    
                val err = Gson().fromJson<Response<Any?>>(
                    it,
                    object : TypeToken<Response<Any?>>() {
    
    }.type
                ).error!!
                withContext(Dispatchers.Main) {
    
    
                    var message=err.message
                    error(Error(err.code, message))
                }
            } catch (e: Throwable) {
    
    
                withContext(Dispatchers.Main) {
    
    
                    error(Error(-1, "数据解析失败"))
                }
            }
        },
        complete = complete
    )
}
/**
 * get请求 通用处理方法
 *
 */
suspend fun <T> get(
    api: suspend CoroutineScope.() -> T,
    success: suspend CoroutineScope.(T) -> Unit,
    error: suspend CoroutineScope.(Error) -> Unit = {
    
    },
    errorParser: suspend CoroutineScope.(String) -> Unit,
    complete: suspend CoroutineScope.() -> Unit = {
    
    }
) {
    
    
    coroutineScope {
    
    
    	//IO线程,耗时操作
        withContext(Dispatchers.IO) {
    
    
            try {
    
    
                val res = api() //请求接口
                withContext(Dispatchers.Main) {
    
    
                    success(res) //成功,切回主线程
                }
            } catch (e: Throwable) {
    
    
            	//统一异常处理
                when (e) {
    
    
                    is HttpException -> {
    
    
                        try {
    
    
                            val body = e.response()!!.errorBody()!!.string()
                            withContext(Dispatchers.Main) {
    
    
                                errorParser(body)
                            }
                        } catch (e: Throwable) {
    
    
                            withContext(Dispatchers.Main) {
    
    
                                error(Error(0, "数据解析失败"))
                            }
                        }
                    }
                    is SocketTimeoutException -> {
    
    
                        withContext(Dispatchers.Main) {
    
    
                            error(Error(0, "连接超时"))
                        }
                    }
                    is ConnectException -> {
    
    
                        withContext(Dispatchers.Main) {
    
    
                            error(Error(0, "请检测网络是否成功连接"))
                        }
                    }
                    is UnknownHostException -> {
    
    
                        withContext(Dispatchers.Main) {
    
    
                            error(Error(0, "请检测网络是否开启"))
                        }
                    }
                    is NetworkErrorException -> {
    
    
                        withContext(Dispatchers.Main) {
    
    
                            error(Error(0, "网络错误"))
                        }
                    }
                    is InterruptedIOException -> {
    
    
                        withContext(Dispatchers.Main) {
    
    
                            error(Error(0, "网络连接超时,请检查网络连接"))
                        }
                    }
                    else -> {
    
    
                        withContext(Dispatchers.Main) {
    
    
                            error(Error(0, "获取数据失败"))
                        }
                    }
                }
            } finally {
    
    
                withContext(Dispatchers.Main) {
    
    
                    complete() 
                }
            }
        }
    }
}


//post方法类似
......

使用

做完这些就可以在ViewModel里面使用了,相比于Rxjava会更加简洁

viewmodel并不是协程所必须创建的,但属于lifecycle的viewmodel,能感知生命周期,在生命周期的末尾会取消掉协程,用viewModelScope创建协程就不用去管内存泄漏这些问题了

class Model(application: Application) : AndroidViewModel(application) {
    
    

    val api by lazy {
    
     RetrofitClient.getInstance().create(Api::class.java) }

	val info= MutableLiveData<UserBean>()
    fun getLeaveDetailExportByte(){
    
    
        viewModelScope.launch {
    
    
            get(
                api={
    
    
                    api.getUserInfo()
                },
                success = {
    
    
                    info.value=it
                },
                error = {
    
    
                    info.value=null
                },
                complete={
    
    }
            )
        }
    }
}

接下来就可以在Activity中调用 ViewModel 的方法去请求数据,并通过observe去监听数据变化并进行相应操作。


总结

这种封装目前没有解决多个接口同时访问,回调冗余的问题(应该可以用协程的async解决吧),同时也请教下大家怎么解决这个问题。只是相对于传统的写法,kotlin 的回调更加简洁。

猜你喜欢

转载自blog.csdn.net/T01151018/article/details/138672445