从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 天,点击检查活动概况”