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

看看效果

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

6、下个华章

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

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

假如您有任何疑问、对文章写的不满意、发现过错或许有更好的办法,欢迎在谈论、私信或邮件中提出,十分感谢您的支撑。

7、感谢

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

“敞开成长之旅!这是我参加「日新计划 2 月更文应战」的第 6 天,点击检查活动概况”