开发搭建网络请求框架 2

从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
                }
            }
        }
    }
}

看看效果

image-20230222113831171

6、下个篇章

因为篇幅原因,我们先到这,我们填了几个坑,也埋下了许许多多的坑,我们也将在这个系列继续一一埋入。

我们的网络框架开始变得复杂起来。现在它可以轻松发起多个请求了,在可扩展性和易用性上有了明显的改变。下篇文章,我们将进一步改善异常处理,拦截处理等

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。?

7、感谢

  1. 校稿:ChatGpt/Bing
  2. 文笔优化:ChatGpt/Bing
  3. 玩安卓API

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYFVayL3' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片