从0.1开发搭建网络请求框架 2
1、前言
这是这个系列的第二篇,阅读本系列文章需要读者对Kotlin、Retrofit、GSON、Flow等技术有一定的了解和基本使用能力。我将从一个非常简单的例子和需求开始,逐步提出更复杂的需求,并一步步改进代码和设计。希望这个系列能为大家带来帮助。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。?
2、RetrofitClient
上文我们说到,为了避免代码中出现过多的重复代码和创建Retrofit实例的混乱情况,可以考虑将Retrofit的创建过程封装成一个单例或静态工厂类,以实现全局统一的Retrofit实例管理。
而我们将创建一个Retrofit管理的单例,使用懒加载方式创建 Retrofit 实例,并提供创建Retrofit 接口的方法
object RetrofitClient {
private const val BASE_URL = "https://www.wanandroid.com/"
private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(createOkHttpClient())
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.build()
}
private fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS).build()
}
fun <S> create(apiClass: Class<S>): S = retrofit.create(apiClass)
/**
* 使用Kotlin的语法糖
*/
inline fun <reified T> create(): T = create(T::class.java)
}
2、网络请求响应结果类
上文我们说到,可以考虑将BannerResponse
和其它类似的XXXResponse
合并成一个通用的基类,例如BaseResponse
,这样可以避免代码中出现过多的重复定义。
data class Response<T>(
private val errorCode: Int? = -9999,
private val errorMsg: String? = "",
private val data: T? = null
) {
fun isSuccess(): Boolean {
return errorCode == 0
}
fun isFailure(): Boolean {
return !isSuccess()
}
fun getCode(): Int {
return errorCode ?: -9999
}
fun getData(): T? {
return data
}
fun getMessage(): String {
return errorMsg ?: ""
}
}
一个通用的网络请求响应结果类,其中 T
是请求返回结果的类型。这个类包含了响应的一些基本信息:
errorCode
表示请求返回的错误码,默认值为-9999
;errorMsg
表示请求返回的错误信息,默认值为空字符串;data
表示请求返回的数据结果,默认值为null
。
这个类还定义了一些用于获取响应信息的方法:
isSuccess()
表示网络请求是否成功,当errorCode
的值为0
时,表示请求成功;isFailure()
表示网络请求是否失败,当errorCode
的值不为0
时,表示请求失败;getCode()
返回网络请求的错误码,如果没有错误码,则返回-9999
;getData()
返回网络请求的数据结果;getMessage()
返回网络请求的错误信息。
这里的各种code和message一般要与服务器对接,这里我们采用玩安卓的的接口响应数据为准
3、通用的网络请求结果类
RequestResult可以将不同的请求结果类型进行分类处理,例如将请求成功、请求失败、请求加载中等不同的结果类型进行分类处理。这样可以帮助开发者更好地了解不同的请求结果状态,并可以根据结果类型在 UI 界面上进行相应的展示。
sealed class RequestResult<out T> {
data class Success<out T>(val data: T) : RequestResult<T>()
data class Error(val errorCode: Int = -1,val errorMsg: String? = "") : RequestResult<Nothing>()
}
一个带有2个嵌套类的密封类,其中 T
是请求返回结果的类型。这个类有两个子类:
Success
表示请求成功,其中包含请求返回的结果数据;Error
表示请求失败,其中包含错误码和错误信息。
Success
类型和 Error
类型都是泛型的,因此可以在实例化这些类型时指定具体的类型参数。Success
类型包含了请求返回的结果数据,可以是任何类型,而 Error
类型不包含结果数据,因此它的类型参数为 Nothing
。
4、请求工具类
最后为了统一使用,我们封装一个工具类
object RequestHelper {
suspend fun <T> request(call: suspend () -> Response<T>): Flow<RequestResult<Response<T>>> {
return flow {
val response: Response<T> = call.invoke()
if (response.isSuccess()) {
emit(RequestResult.Success(response))
} else {
emit(RequestResult.Error(response.getCode(), response.getMessage()))
}
}.flowOn(Dispatchers.IO).catch { throwable: Throwable ->
emit(RequestResult.Error(-1, throwable.message))
}
}
}
request
方法用于请求网络数据,并将请求结果包装成 RequestResult
对象,以便在 UI 界面上展示请求结果状态。该方法接收一个 call
参数,它是一个挂起函数,用于执行实际的网络请求操作。在该方法中,我们首先使用 call.invoke()
执行实际的网络请求操作,并将请求结果赋值给 response
变量。接下来,我们使用 response.isSuccess()
方法来判断网络请求的状态,如果请求成功,则使用 RequestResult.Success(response)
来将请求结果包装成 RequestResult.Success
类型,并将其发送到 Flow 中;如果请求失败,则使用 RequestResult.Error(response.getCode(), response.getMessage())
来将请求结果包装成 RequestResult.Error
类型,并将其发送到 Flow 中。最后,我们使用 flowOn
方法将 Flow 切换到 IO 线程中,以便在 IO 线程中执行网络请求操作。
如果有需要单独处理的特殊操作,可以做出如下修改
suspend fun <T> request(call: suspend () -> Response<T>): Flow<RequestResult<Response<T>>> {
return flow {
val response: Response<T> = call.invoke()
if (response.isSuccess()) {
emit(RequestResult.Success(response))
} else {
if (response.getCode() == -1234) {
emit(RequestResult.TimeOut("超时啦"))
} else {
emit(RequestResult.Error(response.getCode(), response.getMessage()))
}
}
}.flowOn(Dispatchers.IO).catch { throwable: Throwable ->
emit(RequestResult.Error(-1, throwable.message))
}
}
当然,具体什么操作,需要根据实际需求来处理。这里我们只需要打印日志,作为我们的错误处理就够了。
5、新的使用
interface ApiService {
/**
* https://www.wanandroid.com/banner/json
*/
@GET("banner/json")
suspend fun getBanners(): Response<List<Banner>>
//顺手加一个
@GET("friend/json")
suspend fun getFriends(): Response<List<Friend>>
}
class MainArtActivity : AppCompatActivity() {
private lateinit var btnBanner: Button
private lateinit var tvResult: TextView
private val gson = GsonBuilder().setPrettyPrinting().create()
private val apiService: ApiService by lazy { RetrofitClient.create() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_art)
btnBanner = findViewById(R.id.btn_start)
tvResult = findViewById(R.id.tv_result)
val fRequestResult: StringBuilder = StringBuilder()
btnBanner.setOnClickListener {
lifecycleScope.launch {
RequestHelper.request {
apiService.getBanners()
}.collect {
when (it) {
is RequestResult.Success -> {
fRequestResult.append("Result: apiService.getBanners()").append("\n")
.append(gson.toJson(it.data))
}
is RequestResult.Error -> {
fRequestResult.append("ERROR: ").append("\n").append(it.errorCode)
.append(":").append(it.errorMsg)
}
}
tvResult.text = fRequestResult
}
RequestHelper.request {
apiService.getFriends()
}.collect {
when (it) {
is RequestResult.Success -> {
fRequestResult.append("Result: apiService.getFriends()").append("\n")
.append(gson.toJson(it.data))
}
is RequestResult.Error -> {
fRequestResult.append("ERROR: ").append("\n").append(it.errorCode)
.append(":").append(it.errorMsg)
}
}
tvResult.text = fRequestResult
}
}
}
}
}
看看效果
6、下个篇章
因为篇幅原因,我们先到这,我们填了几个坑,也埋下了许许多多的坑,我们也将在这个系列继续一一埋入。
我们的网络框架开始变得复杂起来。现在它可以轻松发起多个请求了,在可扩展性和易用性上有了明显的改变。下篇文章,我们将进一步改善异常处理,拦截处理等
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。?
7、感谢
- 校稿:ChatGpt/Bing
- 文笔优化:ChatGpt/Bing
- 玩安卓API
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情”