开发搭建网络请求框架 3

550 阅读8分钟

从0.1开发搭建网络请求框架 3

1、前言

这是这个系列的第三篇,我们会接着上一篇来,因为间隔的比较久,各位可以返回去看看。阅读本系列文章需要读者对Kotlin、Retrofit、GSON、Flow等技术有一定的了解和基本使用能力。我将从一个非常简单的例子和需求开始,逐步提出更复杂的需求,并一步步改进代码和设计。希望这个系列能为大家带来帮助。然后最近实在是太忙,一直在咕咕咕咕。虽然解放了一会,可惜还是有不少的事情等着我,不过接下来依然会更新的,可能是周/篇?当然如果催的话,那就快快快啦。

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

2、错误处理的改进

RequestHelperrequest 方法中,错误处理是通过捕获异常并发射 RequestResult.Error 实现的。这种做法可能会使得调用方难以处理特定类型的异常。为了改进这一点,可以引入异常类层次结构,以便更精确地传递异常信息。

可以创建一个自定义的基础异常类(例如,ApiException),然后为不同的错误类型创建子类(例如,NetworkExceptionTimeoutException 等)。在 RequestHelper 中,可以根据异常类型创建并抛出相应的子类实例。

我们来添加自定义异常类:

open class ApiException(val code: Int, message: String) : Exception(message)

class NetworkException(code: Int, message: String) : ApiException(code, message)

class TimeoutException(code: Int, message: String) : ApiException(code, message)

RequestResult也需要修改下:

sealed class RequestResult<out T> {
    data class Success<out T>(val data: T) : RequestResult<T>()
    data class Error(val error: Throwable) : RequestResult<Nothing>()
}

RequestHelper当然也是要改一改滴:

object RequestHelper {
    suspend fun <T> request(call: suspend () -> Response<T>): Flow<RequestResult<Response<T>>> {
        return flow<RequestResult<Response<T>>> {
            val response: Response<T> = call.invoke()
            if (response.isSuccess()) {
                emit(RequestResult.Success(response))
            } else {
                when (response.getCode()) {
                    // 根据具体的错误码抛出对应的异常
                    -1 -> throw NetworkException(response.getCode(), response.getMessage())
                    -2 -> throw TimeoutException(response.getCode(), response.getMessage())
                    else -> throw ApiException(response.getCode(), response.getMessage())
                }
            }
        }.catch { throwable: Throwable ->
            emit(RequestResult.Error(throwable))
        }.flowOn(Dispatchers.IO)
    }
}

这样,当调用方接收到 RequestResult.Error 时,它们可以根据异常类型来处理特定的错误情况。例如,可以在 ViewModel 中这样使用:

viewModelScope.launch {
    requestHelper.request { apiService.getBanners() }.collect { result ->
        when (result) {
            is RequestResult.Success -> {
                // 处理成功的响应
            }
            is RequestResult.Error -> {
                when (val error = result.error) {
                    is NetworkException -> {
                        // 处理网络异常
                    }
                    is TimeoutException -> {
                        // 处理超时异常
                    }
                    else -> {
                        // 处理其他异常
                    }
                }
            }
        }
    }
}

敏锐的你发现,那我不是每一次请求都得去处理一堆错误?你确定这不是折磨吗?于是我们需要一个统一处理的办法

3、统一处理特定的错误

为了统一处理特定的错误,例如在服务器返回 -1001 时表示需要登录,我们可以引入一个中间层来处理这些特定的错误。这里我们可以创建一个名为 ApiRequestWrapper 的类,用于处理这些特定的错误。

class ApiRequestWrapper(private val requestHelper: RequestHelper) {
    suspend fun <T> process(call: suspend () -> Response<T>): Flow<RequestResult<Response<T>>> {
        return requestHelper.request(call).map { result ->
            when (result) {
                is RequestResult.Success -> result
                is RequestResult.Error -> {
                    // 处理需要登录的情况,例如跳转到登录页面或显示提示信息
                    when (result.error) {
                        is NetworkException -> {
                            // 处理网络异常
                        }
                        is TimeoutException -> {
                            // 处理超时异常
                        }
                        is LoginException -> {
                            // 处理登陆相关异常
                        }
                        is ApiException -> {
                            // 处理通用异常
                        }
                        else -> {
                            // 处理Android异常
                        }
                    }
                    result
                }
            }
        }
    }
}

当然你的使用也得变一变

class MyViewModel() : ViewModel() {
 		private val apiRequestWrapper: ApiRequestWrapper = ApiRequestWrapper()
    fun fetchData() {
        viewModelScope.launch {
            apiRequestWrapper.process { apiService.getBanners() }.collect { result ->
                when (result) {
                    is RequestResult.Success -> {
                        // 处理成功的响应
                    }
                    is RequestResult.Error -> {
                        // 处理其他错误情况
                    }
                }
            }
        }
    }
}

使用起来就变成了

class MyViewModel(private val apiRequestWrapper: ApiRequestWrapper) : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            apiRequestWrapper.getBanners().collect { result ->
                when (result) {
                    is RequestResult.Success -> {
                        // 处理成功的响应
                    }
                    is RequestResult.Error -> {
                        // 处理其他错误情况
                    }
                }
            }
        }
    }

}

这种将特定错误处理封装到一个类中的方法实际上是一种“适配器模式”(Adapter Pattern)的变种。适配器模式的目的是将一个类的接口转换为客户端期望的另一个接口。在这个例子中,我们创建了一个 ApiRequestWrapper 类,它接受 ApiServiceRequestHelper 作为参数,然后提供了与原始 ApiService 类似的接口,但具有额外的特定错误处理。

通过这种方式,我们将原始的 ApiService 接口适配成了客户端所期望的接口。这使得调用者可以直接使用经过包装的 API 方法,而无需关心底层的特定错误处理逻辑。这样的设计有助于降低模块间的耦合度,提高代码的可维护性和可读性。

请注意,这种实现并不是适配器模式的典型用例,因为它不仅转换了接口,还添加了额外的功能(特定错误处理)。然而,它确实借鉴了适配器模式的核心思想,即通过创建一个包装类来适配现有接口,使其满足客户端的期望。

4、责任链

当项目变得越来越复杂,ApiRequestWrapper 可能会变得很庞大,难以维护。为了解决这个问题,我们可以使用“责任链模式”(Chain of Responsibility Pattern)将错误处理逻辑拆分为多个可组合的处理器。

我们先定义处理 API 异常的责任链中的处理器

interface ErrorHandler {
    var next: ErrorHandler?
    suspend fun handleError(error: ApiException, result: RequestResult.Error): Boolean
}
  1. next: ErrorHandler?:可空类型的 ErrorHandler 属性,用于指向责任链中的下一个处理器。当一个处理器无法处理异常时,它会将异常传递给链中的下一个处理器(通过这个 next 属性)。
  2. suspend fun handleError(error: ApiException, result: RequestResult.Error): Boolean:这是一个挂起函数,用于处理 API 异常。处理器需要实现该函数,并在其中处理特定类型的异常。函数接收两个参数:一个 ApiException(要处理的异常)和一个 RequestResult.Error(包含异常的请求结果)。如果处理器成功处理了异常,该函数应返回 true;否则,返回 false(也可以是决定要不要传递下去)。

再随手定义几个奇怪的错误

class LoginErrorHandler(override var next: ErrorHandler? = null) : ErrorHandler {
    override suspend fun handleError(error: ApiException, result: RequestResult.Error): Boolean {
        return if (error is LoginException) {
            // 处理需要登录的情况,例如跳转到登录页面或显示提示信息
            Log.d("LoginErrorHandler", "处理登陆异常,拦截")
            true
        } else {
            false
        }
    }
}
class NetWorkErrorHandler(override var next: ErrorHandler? = null) : ErrorHandler {
    override suspend fun handleError(error: ApiException, result: RequestResult.Error): Boolean {
        return if (error is NetworkException) {
            if (error.code == 400) {
                Log.d("NetWorkErrorHandler", "客户端请求错误${error.code},不拦截")
            } else {
                Log.d("NetWorkErrorHandler", "处理网络错误${error.code},不拦截")
            }
            false
        } else {
            false
        }
    }
}
class TokenErrorHandler(override var next: ErrorHandler? = null) : ErrorHandler {
    override suspend fun handleError(error: ApiException, result: RequestResult.Error): Boolean {
        return if (error is TokenException) {
            Log.d("TokenErrorHandler", "处理Token错误,拦截")
            true
        } else {
            false
        }
    }
}

因为责任链需要一个个链接起来,所以我们再使用一个构建器类,用于创建和组织 ErrorHandler 接口实现类的责任链。

class ErrorHandlerBuilder {
    private val errorHandlerList: MutableList<ErrorHandler> = mutableListOf()

    fun addErrorHandler(errorHandler: ErrorHandler): ErrorHandlerBuilder {
        errorHandlerList.add(errorHandler)
        return this
    }

    fun build(): MutableList<ErrorHandler> {
        errorHandlerList.reduceRight { left, right ->
            left.apply { next = right }
        }
        return errorHandlerList
    }
}

作为一个懒狗,我们再写一个创建和提供责任链的ErrorHandlerFactory,我们使用这个类隐藏责任链的构建过程

object ErrorHandlerFactory {
    private val errorHandlerChain: MutableList<ErrorHandler> by lazy {
        createErrorHandlerChain()
    }
    private fun createErrorHandlerChain(): MutableList<ErrorHandler> {
        return ErrorHandlerBuilder()
            .addErrorHandler(NetWorkErrorHandler())
            .addErrorHandler(LoginErrorHandler())
            .addErrorHandler(TokenErrorHandler())
            .build()
    }
    fun chain(): MutableList<ErrorHandler> {
        return errorHandlerChain
    }
    fun firstChain(): ErrorHandler {
        return errorHandlerChain.first()
    }
}

5、ApiRequestRepository

ApiRequestWrapper经过我们的改动后,此时此刻它叫ApiRequestWrapper也不太合适了,我们就叫它ApiRequestRepository

来看看,综合了上面代码后的样子

class ApiRequestRepository {
    val errorHandlerChain = ErrorHandlerFactory.chain()
    suspend fun <T> process(call: suspend () -> Response<T>): Flow<RequestResult<Response<T>>> {
        return RequestHelper.request(call).map { result ->
            when (result) {
                is RequestResult.Success -> result
                is RequestResult.Error -> {
                    val error = result.error
                    if (error is ApiException) {
                        propagateError(ErrorHandlerFactory.firstChain(), error, result)
                    } else {
                        result
                    }
                }
            }
        }
    }
    private suspend fun propagateError(
        errorHandler: ErrorHandler?,
        error: ApiException,
        result: RequestResult.Error
    ): RequestResult.Error {
        return if (errorHandler == null || errorHandler.handleError(error, result)) {
            result
        } else {
            propagateError(errorHandler.next, error, result)
        }
    }
}

这样子,我们的拦截器就好了,我们跑一几个测试用例看看


@RunWith(AndroidJUnit4::class)
@SmallTest
class NetTest {
//    private val apiFuckService = spyk<ApiFuckService>()

    val apiService: ApiService = spyk(object: ApiService {
        override suspend fun getBanners(): Response<List<Banner>> {
            return Response(errorCode = 0)
        }

        override suspend fun getFriends(): Response<List<Friend>> {
            return Response(errorCode = 0)
        }

    })
    private val apiRequestRepository = ApiRequestRepository()

    @Before
    fun setup() {
        clearAllMocks()
    }

    @Test
    fun testRequestHelperAndApiRequestRepository() {
        val successResponse = Response<List<Banner>>(errorCode = 0)
        val networkErrorResponse =
            Response<List<Banner>>(errorCode = -1, errorMsg = "Network error")
        val timeoutErrorResponse =
            Response<List<Banner>>(errorCode = -2, errorMsg = "Timeout error")
        val loginErrorResponse = Response<List<Banner>>(errorCode = -3, errorMsg = "Login error")
        val networkClientErrorResponse =
            Response<List<Banner>>(errorCode = 400, errorMsg = "Client error")


        coEvery { apiService.getBanners() } returns successResponse
        testRequestHelperAndApiRequestRepository(successResponse, 0)

        coEvery { apiService.getBanners() } returns networkErrorResponse
        testRequestHelperAndApiRequestRepository(networkErrorResponse, -1)

        coEvery { apiService.getBanners() } returns timeoutErrorResponse
        testRequestHelperAndApiRequestRepository(timeoutErrorResponse, -2)

        coEvery { apiService.getBanners() } returns loginErrorResponse
        testRequestHelperAndApiRequestRepository(loginErrorResponse, -3)

        coEvery { apiService.getBanners() } returns networkClientErrorResponse
        testRequestHelperAndApiRequestRepository(networkClientErrorResponse, 400)
    }

    private fun testRequestHelperAndApiRequestRepository(
        response: Response<List<Banner>>,
        expectedCode: Int
    ) {
        runBlocking {
            // 使用RequestHelper进行请求
            RequestHelper.request { response }.collect { result ->
                when (result) {
                    is RequestResult.Success -> assertEquals(expectedCode, result.data.getCode())
                    is RequestResult.Error -> assertEquals(
                        expectedCode,
                        (result.error as ApiException).code
                    )
                }
            }

            // 使用ApiRequestController进行请求
            apiRequestRepository.process { response }.collect { result ->
                when (result) {
                    is RequestResult.Success -> assertEquals(expectedCode, result.data.getCode())
                    is RequestResult.Error -> assertEquals(
                        expectedCode,
                        (result.error as ApiException).code
                    )
                }
            }
        }
    }
}

image-20230405220625867

非常好,一切正常!

如果你要绕过所有的异常处理那就使用RequestHelper,如果...,自己扩展....

6、脑子停了下

本来要写啥来着,我家的猫,突然给我一下把电脑关了,我直接脑袋宕机。先阉割到这,我去打猫了

N-1、下个篇章

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

我们的网络框架开始变得复杂起来。实际上我们似乎忘了网络请求日志.....嘻嘻嘻,下一章我们将考虑重试机制、数据缓存、日志拦截器、下载等。

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

N、感谢

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