网络请求
网络请求是软件开发中必不可少的一个部分,通过客户端向服务器发送请求,获取服务器上的资源,这些资源可以是网页内容、数据、文件等。通常遵循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 的回调更加简洁。