【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 的回调更加简洁。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
Kotlin是一种基于JVM的静态类型编程语言,可以与Java无缝地进行交互。Retrofit是一个用于构建网络请求框架的库,结合使用KotlinRetrofit可以更加高效地开发网络应用。 使用KotlinRetrofit搭建网络框架的步骤包括: 1. 添加依赖:首先需要在项目中添加KotlinRetrofit的依赖。在项目的build.gradle文件中,添加以下依赖: ```kotlin implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.x.x' // Kotlin标准库依赖 implementation 'com.squareup.retrofit2:retrofit:2.x.x' // Retrofit依赖 ``` 2. 创建API接口:使用Kotlin创建一个包含网络请求接口方法的API接口。在这里可以定义各种需要的请求方式(GET、POST等),以及请求参数和返回数据的定义。例如: ```kotlin interface ApiService { @GET("path/to/api") suspend fun getData(): Response<Data> } ``` 3. 创建Retrofit实例:使用Retrofit的Builder模式创建一个Retrofit实例,并进行必要的配置,如baseUrl、Gson解析器等。例如: ```kotlin val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() ``` 4. 创建API服务实例:使用刚才创建的Retrofit实例,调用create()方法创建一个API服务实例。例如: ```kotlin val apiService = retrofit.create(ApiService::class.java) ``` 5. 发起网络请求:使用API服务实例来发起网络请求,可以通过调用接口方法来发起对应的网络请求。例如: ```kotlin CoroutineScope(Dispatchers.IO).launch { try { val response = apiService.getData() if (response.isSuccessful) { val data = response.body() // 在这里处理返回的数据 } else { // 网络请求失败 } } catch (e: Exception) { // 发生异常 } } ``` 通过以上步骤,我们就可以使用KotlinRetrofit搭建一个简单的网络框架,实现网络请求的发送和数据的解析。Kotlin的语法简洁且易于理解,Retrofit提供了方便的API接口定义和网络请求封装,这让我们能够更加高效地进行网络开发。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

full courage

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值