【Android】kotlin+协程+retrofit+mvvm 优雅地实现并封装网络请求

kotlin的协程可以优化Rxjava冗余,使得网络请求的实现代码更加简洁,可读性也更高。下面将一步步实现网络请求的封装。

之前写过一篇关于kotlin的语法知识,其中有关于这篇内容的协程、高阶函数的知识点:

Kotlin 核心语法详解(快速入门)
Kotlin 协程 高效并发详解( Kotlin Coroutine )

1. 导入依赖

主要是网络请求retrofit和数据解析gson,版本号可灵活

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
 
dependencies {
    implementation 'androidx.core:core-ktx:1.1.0-alpha04'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
    implementation 'com.google.code.gson:gson:2.6.2'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
}

2. Retrofit公共请求类

class RetrofitClient {

	//单例模式
    companion object {
        fun getInstance() = SingletonHolder.INSTANCE

        private lateinit var retrofit: Retrofit
    }

    private object SingletonHolder {
        val INSTANCE = RetrofitClient()
    }

    init {
        retrofit = Retrofit.Builder()
                .client(getOkHttpClient())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(Constant.Config.BASEIP) //服务器地址,如http://xxxxx
                .build()
    }

    private fun getOkHttpClient(): OkHttpClient {
    	//配置请求的日志打印信息
        val httpLoggingInterceptor =
                HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
                    override fun log(message: String) {
                        if (BuildConfig.DEBUG) {
                            Log.i("--", "RetrofitFactory log: $message")
                        }
                    }
                })
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
                .connectTimeout(120, TimeUnit.SECONDS)
                .readTimeout(120, TimeUnit.SECONDS)
                .writeTimeout(120, TimeUnit.SECONDS)
                .connectionPool(ConnectionPool(8, 15, TimeUnit.SECONDS))
                .addInterceptor(httpLoggingInterceptor)
                .addInterceptor(object : Interceptor{
                override fun intercept(chain: Interceptor.Chain): Response {
                    val request = chain.request()
                    return xxx //配置网络拦截器,用来添加token,刷新token等前置操作,这里自定义
                }
            })
                .build()
    }


    /**
     * 创建一个 Service
     * @param service Class<T> 需要创建的 Service
     * @return T Service实例
     */
    fun <T> create(service: Class<T>): T = retrofit.create(service)

	//这个重载方法可以修改服务器地址
    fun <T> createBaseUrl(service: Class<T>,baseUrl:String): T =Retrofit.Builder()
        .client(getOkHttpClient())
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(baseUrl)
        .build()
        .create(service)

}

3. Api接口类

这里需要用到挂起函数,协程体调用外部的方法,它必须是suspend的,异步请求之后处理数据

interface Api {
 @GET("user/xxx")
 suspend fun getUserInfo():BaseResult<UserBean>
}
 
 
//通用数据结构,每个接口返回的数据结构大致需要这些字段
data class BaseResult <T>(
    val code :String,
    val success:Boolean,
    val message:String,
    val time:String,
    val data: T
 
)

4. ViewModel 拓展类

对VM扩展get和post方法,在请求接口时成功和失败的回调更加简洁。complete方法是最后执行的,可以进行加载框的关闭

/**
 * 请求网络数据并处理异常信息
 * @param api  数据请求体
 * @param success 请求成功
 * @param error  请求失败 默认弹出提示
 * @param complete  请求结束
 */
suspend fun <T> AndroidViewModel.get(
    api: suspend CoroutineScope.() -> Response<T>,
    success: suspend CoroutineScope.(T) -> Unit, //无函数体,必传
    error: suspend CoroutineScope.(Error) -> Unit = {}, //有函数体,可以不传
    complete: suspend CoroutineScope.() -> Unit = {}
) {
    get(
        api = api,
        success = {
            if(!it.success){
            	//错误的数据实体Error,只有code,message两个属性
                error(Error(0, it.message?:"获取数据失败"))
            }else if (it.result == null) {
                error(Response.Error(-2, "获取数据为空", null, null))
            } else {
                success(it.result)
            }
        },
        error = error,
        errorParser = {
            try {
                val err = Gson().fromJson<Response<Any?>>(
                    it,
                    object : TypeToken<Response<Any?>>() {}.type
                ).error!!
                withContext(Dispatchers.Main) {
                    var message=err.message
                    error(Error(err.code, message))
                }
            } catch (e: Throwable) {
                withContext(Dispatchers.Main) {
                    error(Error(-1, "数据解析失败"))
                }
            }
        },
        complete = complete
    )
}
/**
 * get请求 通用处理方法
 *
 */
suspend fun <T> get(
    api: suspend CoroutineScope.() -> T,
    success: suspend CoroutineScope.(T) -> Unit,
    error: suspend CoroutineScope.(Error) -> Unit = {},
    errorParser: suspend CoroutineScope.(String) -> Unit,
    complete: suspend CoroutineScope.() -> Unit = {}
) {
    coroutineScope {
    	//IO线程,耗时操作
        withContext(Dispatchers.IO) {
            try {
                val res = api() //请求接口
                withContext(Dispatchers.Main) {
                    success(res) //成功,切回主线程
                }
            } catch (e: Throwable) {
            	//统一异常处理
                when (e) {
                    is HttpException -> {
                        try {
                            val body = e.response()!!.errorBody()!!.string()
                            withContext(Dispatchers.Main) {
                                errorParser(body)
                            }
                        } catch (e: Throwable) {
                            withContext(Dispatchers.Main) {
                                error(Error(0, "数据解析失败"))
                            }
                        }
                    }
                    is SocketTimeoutException -> {
                        withContext(Dispatchers.Main) {
                            error(Error(0, "连接超时"))
                        }
                    }
                    is ConnectException -> {
                        withContext(Dispatchers.Main) {
                            error(Error(0, "请检测网络是否成功连接"))
                        }
                    }
                    is UnknownHostException -> {
                        withContext(Dispatchers.Main) {
                            error(Error(0, "请检测网络是否开启"))
                        }
                    }
                    is NetworkErrorException -> {
                        withContext(Dispatchers.Main) {
                            error(Error(0, "网络错误"))
                        }
                    }
                    is InterruptedIOException -> {
                        withContext(Dispatchers.Main) {
                            error(Error(0, "网络连接超时,请检查网络连接"))
                        }
                    }
                    else -> {
                        withContext(Dispatchers.Main) {
                            error(Error(0, "获取数据失败"))
                        }
                    }
                }
            } finally {
                withContext(Dispatchers.Main) {
                    complete() 
                }
            }
        }
    }
}


//post方法类似
......

5. 使用

做完这些就可以在ViewModel里面使用了,相比于Rxjava会更加简洁

viewmodel并不是协程所必须创建的,但属于lifecycle的viewmodel,能感知生命周期,在生命周期的末尾会取消掉协程,用viewModelScope创建协程就不用去管内存泄漏这些问题了

class Model(application: Application) : AndroidViewModel(application) {

    val api by lazy { RetrofitClient.getInstance().create(Api::class.java) }

	val info= MutableLiveData<UserBean>()
    fun getLeaveDetailExportByte(){
        viewModelScope.launch {
            get(
                api={
                    api.getUserInfo()
                },
                success = {
                    info.value=it
                },
                error = {
                    info.value=null
                },
                complete={}
            )
        }
    }
}

接下来就可以在Activity中调用 ViewModel 的方法去请求数据,并通过observe去监听数据变化并进行相应操作。


总结

这种封装目前没有解决多个接口同时访问,回调冗余的问题(应该可以用协程的async解决吧),同时也请教下大家怎么解决这个问题。只是相对于传统的写法,kotlin 的回调更加简洁。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值