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>() {
            @Override
            public void onSubscribe(Disposable d) {
                
            }

            @Override
            public void onNext(HttpResult httpResult) {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public 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.INSTANCE
        private 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 {
        @Volatile
        private var httpUtil: HttpUtil? = null

        fun 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? = null
    val errorCode: Int = 0
    val 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>()//是否显示loading
    var 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

你可能感兴趣的:(Android笔记,Android)