MVVM+Retrofit+Kotlin网络框架封装
上篇文章讲了MVVM入门,网络请求部分非常简单和原始,本篇则是上一篇的进阶,主要讲解如何在vm中使用协程结合Retrofit进行网络框架的封装。
GitHub完整版:https://github.com/baiyuliang/MVVM
Retrofit自不必说,非常优秀的网络请求框架,说到Retrofit就不得不提RxJava,
RxJava是什么?
官方定义:一个在jvm上使用可观测的序列来组成异步的,基于事件的程序的库,它具有良好的链式编程风格,以及强大的异步处理能力,在近几年的移动开发中异常火爆,常结合Retrofit,以及MVP设计模式组成移动开发架构,搜索一下就会有大量相关文章呈现在你的眼前,不可否认,它是那么的优秀!Rxjava最重要的思想,就是观察者模式,我敢打赌,到现在还会有很多同学仍然没有理解观察者,订阅者这些到底是什么玩意,即便你已经使用RxJava几年了。我在这里只简单介绍下,你就会很好的理解了:
首先,定义接口(一个简单的例子):
ApiService.java
@GET("test")Observable<HttpResult> test(@QueryMap HashMap<String, String> options);
我们可以看到这个方法的返回值Observable,我们可以将其简单的理解为被观察者,这个被观察者 就是接口请求方法,观察者需要观察的就是你这个方法请求的结果的变动。
当我们用Presenter去请求接口时,会有一个中间过程,一般用来传参,大致类似于:
TestNetUtil.java
public Observable<HttpResult> test(String id) {LinkedHashMap<String, String> params = new LinkedHashMap<>();params.put("id", id);return ApiUtil.getInstance().getApiService().test(params);}
注意看返回值,仍然是Observable,继续往下走就是在Presenter中调用了,大致类似于这样:
TestPresenter
public void test(String id) {TestNetUtil.newInstance().test(id).subscribe(new Observer<HttpResult>() {@Overridepublic void onSubscribe(Disposable d) {}@Overridepublic void onNext(HttpResult httpResult) {}@Overridepublic void onError(Throwable e) {}@Overridepublic void onComplete() {}});}
这样就完成了一个接口请求,注意上面的方法,TestNetUtil.newInstance().test(id)我们知道,其返回值是Observable,我们说过这是一个被观察者,就是接口请求本身,subscribe订阅,该方法传入了一个Observer对象,Observer就是观察者,这个观察者订阅了(subscribe)被观察者(TestNetUtil.newInstance().test(id)),被观察者一旦接口请求结果完成,就会通知被观察者去处理结果,因为我订阅了你,你有任何变动我都会知道,这就是RxJava的观察者模式,而ViewModel中也是一样的道理!
好了,继续,本篇博客将不会去讲Rxjava与协程孰优孰劣,只讲如何使用协程,像RxJava一样,更高效的完成网络数据请求,当然,具体什么是协程,本篇也不会细讲,大家可自行查询相关文档!
网络请求框架封装第一步(老生常谈),创建ApiService:
interface ApiService {@GET("banner/json")suspend fun getBanner(): BaseResult<List<BannerBean>>@GET("article/listproject/{page}/json")suspend fun getArticleList(@Path("page") page: Int): BaseResult<ArticleListBean>}
注意suspend ,这个是协程的重要关键字,简单的理解就是,它的作用等同于一个方法的回调(Callbak),举个例子:
fun test1():String{var result//...return result
}
fun test2(param:String){var result//...//需要使用test1的返回的结果return result
}
看这两个方法,方法2需要使用方法1返回的结果,如果两个都是异步请求,通常的写法如下:
fun test(){test1(object:CallBack(param){//请求完成回调test2(param)//在回调中调用方法2,如果有方法3.4...,则进入了回调地狱})
}
而使用suspend:则只需
suspend fun test(){var param=test1()var result=test2(param)var xx=tests(result)
}
即可,大家可以搜索更多相关文章去深入理解,现在只要记住在接口方法前要加上这个字段!
第二步,配置Retrofit:
class RetrofitClient {companion object {fun getInstance() = SingletonHolder.INSTANCEprivate lateinit var retrofit: Retrofit}private object SingletonHolder {val INSTANCE = RetrofitClient()}init {retrofit = Retrofit.Builder().client(getOkHttpClient()).addConverterFactory(GsonConverterFactory.create()).baseUrl(BASE_URL).build()}private fun getOkHttpClient(): OkHttpClient {return OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).addInterceptor(LoggingInterceptor()).build()}fun create(): ApiService = retrofit.create(ApiService::class.java)}
第三步,封装接口调用工具类HttpUtil:
class HttpUtil {private val mService by lazy { RetrofitClient.getInstance().create() }suspend fun getBanner() = mService.getBanner()suspend fun getArticleList(page: Int) = mService.getArticleList(page)/*** 单例模式*/companion object {@Volatileprivate var httpUtil: HttpUtil? = nullfun getInstance() = httpUtil ?: synchronized(this) {httpUtil ?: HttpUtil().also { httpUtil = it }}}}
第四步,经过以上三步,其实已经初步完成了对Retrofit的封装,我们来在ViewModel中试着调用一下:
class MainViewModel : ViewModel() {//MutableLiveData<>传入的数据类型,要与Service中BaseResult<>保证一致var bannerData = MutableLiveData<List<BannerBean>>()var articlesData = MutableLiveData<ArticleListBean>()fun getBannerTest() {viewModelScope.launch {withContext(Dispatchers.IO) {//异步请求接口val result = HttpUtil.getInstance().getBanner()withContext(Dispatchers.Main) {if (result.errorCode == 0) {//请求成功bannerData.value = result.data}}}}}fun getArticleListTest(page:Int) {viewModelScope.launch {withContext(Dispatchers.IO) {//异步请求接口val result = HttpUtil.getInstance().getArticleList(page)withContext(Dispatchers.Main) {if (result.errorCode == 0) {//请求成功articlesData.value = result.data}}}}}}
虽然已经封装了HttpUtil,但我们可以看到,这样调用,每个方法中都要写launch ,以及对结果的处理,这并不是一个理想的网络框架封装,我们要做的是将launch部分提取出来,并对结果统一处理,那么写一个BaseViewModel,将网络请求过程放在BaseViewModel里面,不失为一个好办法。但是,要想写一个统一的网络请求方法,具体该怎么写呢?说到这里,就不得不说Kotlin语法的一个新特性,它可以将一个函数作为一个参数,传入另一个函数,跟js一毛一样,太方便了,举个例子:
fun test() {log("test")}fun test2(t: () -> Unit) {t()}test2({test()})
调用结果会执行log(“test”)!
-> Unit表示该方法无返回值;
-> 具体数据类型,则该方法返回该数据类型数据;
例如:
fun test() :String{log("test")return "result"}fun test2(t: () -> String) {var result=t()log(result)}
调用 test2({test()})结果:
test
result
因此,我们可以将接口请求,也就是“HttpUtil.getInstance().getBanner()”这一部分作为一个参数传入统一请求方法中,具体如下:
fun launch(api: suspend CoroutineScope.() -> Unit) {viewModelScope.launch {withContext(Dispatchers.IO) {//异步请求接口val result = api()withContext(Dispatchers.Main) {//处理result}}}}
调用的时候,只需:
launch { HttpUtil.getInstance().getBanner() }
是不是很简单,但还没有完成,因为我们没有对返回结果result进行处理,好办,再传入一个success方法作为结果处理回调:
fun <T>launch(api: suspend CoroutineScope.() -> BaseResult<T>,success:CoroutineScope.(T) -> Unit) {viewModelScope.launch {withContext(Dispatchers.IO) {//异步请求接口val result = api()withContext(Dispatchers.Main) {result.data?.let { success(it) }}}}}
调用:
launch({HttpUtil.getInstance().getBanner()},{bannerData.value=it})
注意泛型T,就是BaseResult中的data:
open class BaseResult<T> {val errorMsg: String? = nullval errorCode: Int = 0val data: T? = null
}
第二个参数方法,也就是success:
{bannerData.value=it}
中的it就是返回的data,貌似到此,ViewModel中的网络请求已经足够简单了,但是还不够理想,因为我连success回调都不想写(下面是最终完整版方法):
BaseViewModel:
open class BaseViewModel : ViewModel() {val httpUtil by lazy { HttpUtil.getInstance() }var isShowLoading = MutableLiveData<Boolean>()//是否显示loadingvar errorData = MutableLiveData<ErrorResult>()//错误信息private fun showLoading() {isShowLoading.value = true}private fun dismissLoading() {isShowLoading.value = false}private fun showError(error: ErrorResult) {errorData.value = error}private fun error(errorResult: ErrorResult) {showError(ErrorResult(errorResult.code, errorResult.errMsg))}/*** 注意此方法传入的参数:api是以函数作为参数传入* api:即接口调用方法* error:可以理解为接口请求失败回调* ->数据类型,表示方法返回该数据类型* ->Unit,表示方法不返回数据类型*/fun <T> launch(api: suspend CoroutineScope.() -> BaseResult<T>,//请求接口方法,T表示data实体泛型,调用时可将data对应的bean传入即可liveData: MutableLiveData<T>,isShowLoading: Boolean = false) {if (isShowLoading) showLoading()viewModelScope.launch {try {withContext(Dispatchers.IO) {//异步请求接口val result = api()withContext(Dispatchers.Main) {if (result.errorCode == 0) {//请求成功liveData.value = result.data} else {error(ErrorResult(result.errorCode, result.errorMsg))}}}} catch (e: Throwable) {//接口请求失败error(ErrorUtil.getError(e))} finally {//请求结束dismissLoading()}}}}
MainViewModel :
class MainViewModel : BaseViewModel() {//MutableLiveData<>传入的数据类型,要与Service中BaseResult<>保证一致var bannerData = MutableLiveData<List<BannerBean>>()var articlesData = MutableLiveData<ArticleListBean>()fun getBanner() {launch({ httpUtil.getBanner() }, bannerData)}fun getArticleList(page: Int) {launch({ httpUtil.getArticleList(page) }, articlesData, true)}}
到此,算是完成了一个真正具有使用价值的网络请求框架!
项目下载链接:https://download.csdn.net/download/baiyuliang2013/12361150
MVVM+Retrofit+Kotlin网络框架封装相关推荐
- 【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )
OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...
- 上门洗车APP --- Android客户端开发 之 网络框架封装介绍(一)
上门洗车APP --- Android客户端开发 之 网络框架封装介绍(一) 上篇文章中给大家简单介绍了一些业务,上门洗车APP --- Android客户端开发 前言及业务简介,本篇文章给大家介绍下 ...
- iOS swift Alamofire+HandyJSON网络框架封装
iOS swift Alamofire+HandyJSON网络框架封装 我们在学习Objective_C时使用的网络框架是AFNetworking+MJExtension,而在swift中Alamof ...
- Android之全面解析Retrofit网络框架封装库
转载请标明出处:[顾林海的博客] 前言 Retrofit是Square公司推出的一个HTTP的框架,主要用于Android和Java,Retrofit会将每一个HTTP的API请求变成一个Java的接 ...
- Retrofit的网络框架介绍
Retrofit简介 Retrofit是square开源的网络请求库,底层是使用OKHttp封装的,网络请求速度很快. 主要有一下几种请求方法 格式 含义 @GET 表示这是一个GET请求 @POST ...
- java retrofit_Android开发Retrofit2+Rxjava2+okHttp 网络框架封装
释放双眼,带上耳机,听听看~! 说明 RxJava的概念其实很模糊,我对它的理解就是一个给你方便处理异步问题的框架,到底有多方便,体会过才知道... Retrofit就是对okhttp做了一层封装.把 ...
- Retrofit2+Rxjava2+okHttp 网络框架封装
说明 RxJava的概念其实很模糊,我对它的理解就是一个给你方便处理异步问题的框架,到底有多方便,体会过才知道... Retrofit就是对okhttp做了一层封装.把网络请求都交给给了Okhttp, ...
- 最简易的网络框架封装(新手可看)
网络通信在Android上的重要性就不多说了. demo就是获取到"https://www.baidu.com/"的HTML代码后显示在textview中,如下图:(源码在文章结尾 ...
- dio网络框架封装_Flutter 使用dio来发起网络请求以及Cookie管理
前言 Flutter官方建议您使用 dio 来发起网络请求,在学习过程中,也尝试过用dart io中的HttpClient发起的请求,这里主要讲一下dio的使用以及CookieJar.CookieMa ...
最新文章
- 算法----- 在排序数组中查找元素的第一个和最后一个位置
- 2019 训练比赛 记录
- mysql now unixtime_MySQL时间函数from_unixtime()date_format()unix_timestamp()now()使用说明
- 蓝桥杯 子串分值 递推
- 【生活科普】这7个影视剧的经典桥段,骗了我们很多年……
- 一个社交电商小程序配套的平台接
- C语言 文件缓冲区
- pytorch—torch.tensor.scatter操作解析
- 【数学信号处理】基于matlab数字信号频谱分析【含Matlab源码 1544期】
- gns3中怎么把服务器虚拟化,GNS3使用详解(gns3如何模拟ids)
- 帝骑k触屏模拟器_假面骑士decade神主牌模拟器手机版下载|
- 阳历转阴历,阳历转中国农历
- 网卡的功能主要有两个
- 数据结构练习题――中序遍历二叉树
- dcat-admin oss图片上传
- (一)、写一个怪物的类,类中有属性姓名(name),攻击力(attack),有打人的方法(fight)。(方法的重写)...
- 2022年上半年软件设计师考试下午真题(专业解析+参考答案)
- const定义及初始化约束
- plot fplot ezplot
- 一个人并不寂寞,想一个人才寂寞:QQ空间情感日志