這篇文章主要是介紹如何去封裝一個基於retorfit、協程的網絡請求,封裝以後並非一個標準的DSL形式,關於如何去改變風格,變成一個DSL的網絡請求,能夠看這篇文章:像使用gradle同樣,在kotlin中進行網絡請求html
協程正式版出來已經有一段時間,相對於線程,協程佔用更小的資源同時也能夠更加方便的進行各個線程的切換。從retrofit2.6.0開始,retrofit直接能夠支持哦誒和協程的使用。那麼接下來就給你們展現一下如何快速封裝一個基於協程的dsl形式的請求方法。
文章內容以目前較爲經常使用的mvp架構爲例。java
從這個最基本的請求能夠看出:node
相對於rxjava的方式,這種方式更加的簡單,最簡單的時候一個request以及then兩個操做符就能夠進行一次網絡請求。同時這種方式是能夠防止內存泄露的,能夠在activity已經finish的時候自動取消請求,而rxjava若是須要進行防止內存泄漏,須要較爲複雜的處理。git
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)
}
}
}
複製代碼
主要注意的一點是,網絡請求的接口方法須要用suspend修飾,由於改方法須要在協程內進行,不然請求的時候會報錯。api
interface ApiService {
@GET("users/JavaNoober/repos")
suspend fun getUserInfo(): List<UserBean>
}
複製代碼
由於須要對請求進行頁面結束的時候自動取消處理,這裏使用google提供的lifecycle庫進行封裝,由於在高版本的support庫中,lifecycle已經默認集成在裏面,這樣使用更加的方便,幾行代碼就你完成所需功能。緩存
/**
* 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操做。安全
/**
* 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對象能夠用來控制自動取消。網絡
/**
* 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是在子協程中,因此不會有線程阻塞的問題。
請求完成後,咱們就能夠對回調結果進行一系列處理。架構
由於文章是基於常見對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同時定義爲LifecycleOwner和LifecycleObserver。
定義爲LifecycleObserver,是爲了讓presenter能夠對Activity的生命週期進行監聽。
定義爲LifecycleOwner是爲了在其中進行網絡請求,咱們在重寫的onDestroy內加入:
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
複製代碼
這樣Activity進行onDestroy的時候,就會走以前定義的CoroutineLifecycleListener中cancelCoroutine方法,去取消協程。
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中進行網絡請求