DSL形式的基于retorfit、协程的网络请求封装

这篇文章主要是介绍如何去封装一个基于retorfit、协程的网络请求,封装以后并非一个标准的DSL形式,关于如何去改变风格,变成一个DSL的网络请求,能够看这篇文章:像使用gradle同样,在kotlin中进行网络请求html

前言

协程正式版出来已经有一段时间,相对于线程,协程占用更小的资源同时也能够更加方便的进行各个线程的切换。从retrofit2.6.0开始,retrofit直接能够支持哦诶和协程的使用。那么接下来就给你们展现一下如何快速封装一个基于协程的dsl形式的请求方法。
文章内容以目前较为经常使用的mvp架构为例。java

封装后的请求方式

从这个最基本的请求能够看出:node

  • start为主线程操做,咱们能够进行一些ui操做好比弹窗等;固然若是不须要直接进行第二步request操做也能够
  • request为网络请求操做,该线程为子线程
  • then为网络请求结果,有onError和onSucces以及onComplete方法,为主线程操做
  • 最后一个大括号内为onCompete方法,有没有都行,这里是kotlin的lambda形式的写法

相对于rxjava的方式,这种方式更加的简单,最简单的时候一个request以及then两个操做符就能够进行一次网络请求。同时这种方式是能够防止内存泄露的,能够在activity已经finish的时候自动取消请求,而rxjava若是须要进行防止内存泄漏,须要较为复杂的处理。git

retrofit

RetrofitHelper

retrofit再也不多说,这里建立一个RetrofitHelper类用于进行网络请求。这里为了方便就直接建立类一个retorfit的对象,并无进行缓存什么的。开发者能够根据本身的须要去进行一些封装。主要区别是,基于协程的retrofit网络请求,不须要像rxjava同样在建立retrofit的时候add一个adapter,直接建立便可。github

class RetrofitHelper {


    companion object{
        fun getApi(): ApiService{
            val client = OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)       //链接超时
                .readTimeout(10, TimeUnit.SECONDS)          //读取超时
                .writeTimeout(10, TimeUnit.SECONDS)         //写超时
                .build()
            val retrofit = Retrofit.Builder().client(client).baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create()).build()

            return retrofit.create(ApiService::class.java)
        }
    }
}
复制代码

ApiService

主要注意的一点是,网络请求的接口方法须要用suspend修饰,由于改方法须要在协程内进行,不然请求的时候会报错。api

interface ApiService {

    @GET("users/JavaNoober/repos")
    suspend fun getUserInfo(): List<UserBean>
}
复制代码

CoroutineDSL

由于须要对请求进行页面结束的时候自动取消处理,这里使用google提供的lifecycle库进行封装,由于在高版本的support库中,lifecycle已经默认集成在里面,这样使用更加的方便,几行代码就你完成所需功能。缓存

start

/**
 * execute in main thread
 * @param start doSomeThing first
 */
infix fun LifecycleOwner.start(start: (() -> Unit)): LifecycleOwner{
    GlobalScope.launch(Main) {
        start()
    }
    return this
}
复制代码

在LifecycleOwner中添加start方法,开启协程,在Main协程中执行一些ui操做。安全

request

/**
 * execute in io thread pool
 * @param loader http request
 */
infix fun <T> LifecycleOwner.request(loader: suspend () -> T): Deferred<T> {
    return request(loader)
}

/**
 * execute in io thread pool
 * @param loader http request
 * @param needAutoCancel need to cancel when activity destroy
 */
fun <T> LifecycleOwner.request(loader: suspend () -> T, needAutoCancel: Boolean = true): Deferred<T> {
    val deferred = GlobalScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
        loader()
    }
    if(needAutoCancel){
        lifecycle.addObserver(CoroutineLifecycleListener(deferred, lifecycle))
    }
    return deferred
}

internal class CoroutineLifecycleListener(private val deferred: Deferred<*>, private val lifecycle: Lifecycle): LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun cancelCoroutine() {
        if (deferred.isActive) {
            deferred.cancel()
        }
        lifecycle.removeObserver(this)
    }
}
复制代码

***request***方法是进行网络请求的方法,咱们能够经过一个boolean值来控制是否须要自动取消。若是须要,咱们则为当前LifecycleOwner增长一个LifecycleObserver,在onDestroy的时候自动取消请求便可。
***loader***参数为retrofit的网络请求方法,将其放入协程提供的io线程中进行操做,返回一个deferred对象,deferred对象能够用来控制自动取消。网络

then

/**
 * execute in main thread
 * @param onSuccess callback for onSuccess
 * @param onError callback for onError
 * @param onComplete callback for onComplete
 */
fun <T> Deferred<T>.then(onSuccess: suspend (T) -> Unit, onError: suspend (String) -> Unit, onComplete: (() -> Unit)? = null): Job {
    return GlobalScope.launch(context = Main) {
        try {
            val result = this@then.await()
            onSuccess(result)
        } catch (e: Exception) {
            e.printStackTrace()
            when (e) {
                is UnknownHostException -> onError("network is error!")
                is TimeoutException -> onError("network is error!")
                is SocketTimeoutException -> onError("network is error!")
                else -> onError("network is error!")
            }
        }finally {
            onComplete?.invoke()
        }
    }
}
复制代码

then方法是对请求结果的处理,方法内传入onSuccess、onError、onCompete的回调,onComplete为可选参数,默认为null。
为Deferred加入then方法,咱们在最外层开启主协程,而后在主协程内首先调用await方法,其实也就是将上面request中的defereed进行网络请求操做,而defereed是在子协程中,因此不会有线程阻塞的问题。
请求完成后,咱们就能够对回调结果进行一系列处理。架构

Presenter

由于文章是基于常见对mvp架构形式,因此咱们将网络请求方法对位置放于Presenter中,上面封装的网络请求方法是放在Lifecycle中,而Presenter是没有实现Lifecycle方法的,所以咱们须要进行一些封装,这里我直接将其放在MainPresenter中,实际开发的时候能够写在BasePresenter中,防止每次都要写一遍。

class MainPresenter : DefaultLifecycleObserver, LifecycleOwner {
    private val TAG = "MainPresenter"

    private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle = lifecycleRegistry

    override fun onDestroy(owner: LifecycleOwner) {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        lifecycle.removeObserver(this)
    }
}

class MainActivity : AppCompatActivity() {

    private val mainPresenter: MainPresenter by lazy { MainPresenter() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycle.addObserver(mainPresenter)
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycle.removeObserver(mainPresenter)
    }
}
复制代码

咱们将Presenter同时定义为LifecycleOwnerLifecycleObserver
定义为LifecycleObserver,是为了让presenter能够对Activity的生命周期进行监听。
定义为LifecycleOwner是为了在其中进行网络请求,咱们在重写的onDestroy内加入:

lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
复制代码

这样Activity进行onDestroy的时候,就会走以前定义的CoroutineLifecycleListenercancelCoroutine方法,去取消协程。

完整的调用过程

class MainActivity : AppCompatActivity() {

    private val mainPresenter: MainPresenter by lazy { MainPresenter() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycle.addObserver(mainPresenter)
        mainPresenter.doHttpRequest()
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycle.removeObserver(mainPresenter)
    }
}

class MainPresenter : DefaultLifecycleObserver, LifecycleOwner {
    private val TAG = "MainPresenter"

    private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle = lifecycleRegistry

    override fun onDestroy(owner: LifecycleOwner) {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        lifecycle.removeObserver(this)
    }

    /**
     * 打印结果以下:
     *
     * MainPresenter: start doHttpRequest:currentThreadName:main
     * MainPresenter: request doHttpRequest:currentThreadName:DefaultDispatcher-worker-2
     * MainPresenter: onSuccess doHttpRequest:currentThreadName:main
     * MainPresenter: UserBean(login=null, id=61097549, node_id=MDEwOlJlcG9zaXRvcnk2MTA5NzU0OQ==, avatar_url=null, gravatar_id=null, url=https://api.github.com/repos/JavaNoober/Album, html_url=https://github.com/JavaNoober/Album, followers_url=null, following_url=null, gists_url=null, starred_url=null, subscriptions_url=null, organizations_url=null, repos_url=null, events_url=https://api.github.com/repos/JavaNoober/Album/events, received_events_url=null, type=null, site_admin=false, name=Album, company=null, blog=null, location=null, email=null, hireable=null, bio=null, public_repos=0, public_gists=0, followers=0, following=0, created_at=2016-06-14T06:28:05Z, updated_at=2016-06-14T06:40:26Z)
     * MainPresenter: onComplete doHttpRequest:currentThreadName:main
     */
    fun doHttpRequest() {
        start {
            Log.e(TAG, "start doHttpRequest:currentThreadName:${Thread.currentThread().name}")
        }.request {
            Log.e(TAG, "request doHttpRequest:currentThreadName:${Thread.currentThread().name}")
            RetrofitHelper.getApi().getUserInfo()
        }.then(onSuccess = {
            Log.e(TAG, "onSuccess doHttpRequest:currentThreadName:${Thread.currentThread().name}")
            Log.e(TAG, it[0].toString())
        }, onError = {
            Log.e(TAG, "onError doHttpRequest:currentThreadName:${Thread.currentThread().name}")
        }) {
            Log.e(TAG, "onComplete doHttpRequest:currentThreadName:${Thread.currentThread().name}")
        }
    }
}
复制代码

上述封装完以后,就能够进行网络请求了。实际开发过程当中,能够对其进行进一步封装。

总结

全部的核心代码加起来也不过100行左右,可是就完成了一个安全轻量简便的网络请求操做。由于在这里使用了Lifecycle,其实后续也能够考虑使用配合LiveData,这样更能够达到其余框架难以实现的对数据生命周期的处理。
完整的代码已上传至github(CoroutinesHttp),欢迎你们提出更好的建议方法。
关于如何去改变风格,变成一个DSL的网络请求,能够看这篇文章:像使用gradle同样,在kotlin中进行网络请求

相关文章
相关标签/搜索