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中進行網絡請求

相關文章
相關標籤/搜索