https://github.com/ReactiveX/...html
RxKotlin: RxJava bindings for Kotlinreact
咱們如今已經基本知道 Kotlin 中 DSL 的樣子了。可是這些 DSL 都是怎樣實現的呢?本節咱們就經過實現一個極簡的http DSL來學習建立 DSL 背後的基本原理。jquery
在這裏咱們對 OkHttp 作一下簡單的封裝,實現一個相似 jquery 中的 Ajax 的 http 請求的DSL。git
OkHttp 是一個成熟且強大的網絡庫,在Android源碼中已經使用OkHttp替代原先的HttpURLConnection。不少著名的框架例如Picasso、Retrofit也使用OkHttp做爲底層框架。github
提示: 更多關於OkHttp 的使用可參考: http://square.github.io/okhttp/ajax
咱們首先使用 IDEA 建立 Kotlin Gradle 項目編程
而後,在 build.gradle 裏面配置依賴json
compile 'com.github.ReactiveX:RxKotlin:2.1.0' compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.8.1' compile group: 'com.alibaba', name: 'fastjson', version: '1.2.35'
其中,RxKotlin是ReactiveX 框架對 Kotlin 語言的支持庫。咱們這裏主要用RxKotlin來進行請求回調的異步處理。網絡
咱們使用的是 'com.github.ReactiveX:RxKotlin:2.1.0' , 這個庫是在 https://jitpack.io 上,因此咱們在repositories配置裏添加 jitpack 倉庫架構
repositories { maven { url 'https://jitpack.io' } ... }
ReactiveX是Reactive Extensions的縮寫,通常簡寫爲Rx,最初是LINQ的一個擴展,由微軟的架構師Erik Meijer領導的團隊開發,在2012年11月開源。
Rx擴展了觀察者模式用於支持數據和事件序列。Rx是一個編程模型,目標是提供一致的編程接口,幫助開發者更方便的處理異步I/O(非阻塞)數據流。
Rx庫支持.NET、JavaScript和C++ 。Rx近幾年愈來愈流行,如今已經支持幾乎所有的流行編程語言了。一個語言列表以下所示:
Rx的大部分語言庫由ReactiveX這個組織負責維護。Rx 比較流行的庫有RxJava/RxJS/Rx.NET等,固然將來RxKotlin也必將更加流行。
提示: Rx 的社區網站是: http://reactivex.io/ 。 Github 地址:https://github.com/ReactiveX/
首先咱們設計Http請求對象封裝類以下
class HttpRequestWrapper { var url: String? = null var method: String? = null var body: RequestBody? = null var timeout: Long = 10 internal var success: (String) -> Unit = {} internal var fail: (Throwable) -> Unit = {} fun success(onSuccess: (String) -> Unit) { success = onSuccess } fun error(onError: (Throwable) -> Unit) { fail = onError } }
HttpRequestWrapper的成員變量和函數說明以下表
成員 | 說明 |
---|---|
url | 請求 url |
method | 請求方法,例如 Get、Post 等,不區分大小寫 |
body | 請求頭,爲了簡單起見咱們直接使用 OkHttp的RequestBody類型 |
timeout | 超時時間ms,咱們設置了默認值是10s |
success | 請求成功的函數變量 |
fail | 請求失敗的函數變量 |
fun success(onSuccess: (String) -> Unit) | 請求成功回調函數 |
fun error(onError: (Throwable) -> Unit) | 請求失敗回調函數 |
咱們直接調用 OkHttp 的 Http 請求 API
private fun call(wrap: HttpRequestWrapper): Response { var req: Request? = null when (wrap.method?.toLowerCase()) { "get" -> req = Request.Builder().url(wrap.url).build() "post" -> req = Request.Builder().url(wrap.url).post(wrap.body).build() "put" -> req = Request.Builder().url(wrap.url).put(wrap.body).build() "delete" -> req = Request.Builder().url(wrap.url).delete(wrap.body).build() } val http = OkHttpClient.Builder().connectTimeout(wrap.timeout, TimeUnit.MILLISECONDS).build() val resp = http.newCall(req).execute() return resp }
它返回請求的響應對象Response。
咱們在OkHttpClient.Builder().connectTimeout(wrap.timeout, TimeUnit.MILLISECONDS).build()
中設置超時時間的單位是 TimeUnit.MILLISECONDS
。
咱們經過wrap.method?.toLowerCase()
處理請求方法的大小寫的兼容。
咱們首先新建一個數據發射源:一個可觀察對象(Observable),做爲發射數據用
val sender = Observable.create<Response>({ e -> e.onNext(call(wrap)) })
其中,e 的類型是 io.reactivex.Emitter
(發射器),它的接口定義是
public interface Emitter<T> { void onNext(@NonNull T value); void onError(@NonNull Throwable error); void onComplete(); }
其方法功能簡單說明以下:
方法 | 功能 |
---|---|
onNext | 發射一個正常值數據(value) |
onError | 發射一個Throwable異常 |
onComplete | 發射一個完成的信號 |
這裏,咱們經過調用onNext方法,把 OkHttp 請求以後的響應對象Response 做爲正常值發射出去。
而後咱們再建立一個數據接收源:一個觀察者(Observer)
val receiver: Observer<Response> = object : Observer<Response> { override fun onNext(resp: Response) { wrap.success(resp.body()!!.string()) } override fun onError(e: Throwable) { wrap.fail(e) } override fun onSubscribe(d: Disposable) { } override fun onComplete() { } }
receiver 的 onNext 函數接收 sender 發射過來的數據 Response, 而後咱們在函數體內,調用這個響應對象,給 wrap.success 回調函數進行相關的賦值操做。一樣的,onError 函數中也執行相應的賦值操做。
最後,經過 subscribe 訂閱函數來綁定 sender 與 receiver 的關聯:
sender.subscribe(receiver)
做爲接收數據的 receiver (也就是 觀察者 (Observer) ),對發送數據的 sender (也就是可被觀察對象( Observable)) 所發射的數據或數據序列做出響應。
這種模式能夠極大地簡化併發操做,由於它建立了一個處於待命狀態的觀察者,在將來某個時刻響應 sender 的通知,而不須要阻塞等待 sender 發射數據。這個很像協程中的通道編程模型。
咱們的ajax DSL主函數設計以下:
fun ajax(init: HttpRequestWrapper.() -> Unit) { val wrap = HttpRequestWrapper() wrap.init() doCall(wrap) }
其中,參數init: HttpRequestWrapper.() -> Unit
是一個帶接收者的函數字面量,它的類型是init = Function1<com.kotlin.easy.HttpRequestWrapper, kotlin.Unit>
。 HttpRequestWrapper是擴展函數 init()
的接收者,點號 .
是擴展函數修飾符。
咱們在函數體內直接調用了這個函數字面量 wrap.init()
。這樣的寫法可能比較難以理解,這個函數字面量 init 的調用其實是 init.invoke(wrap)
,就是把傳入 ajax 的函數參數直接傳遞給 wrap 。爲了更簡單的理解這個 init 函數的工做原理,咱們經過把上面的 ajax 函數的代碼反編譯成對應的 Java 代碼以下:
public static final void ajax(@NotNull Function1 init) { Intrinsics.checkParameterIsNotNull(init, "init"); HttpRequestWrapper wrap = new HttpRequestWrapper(); init.invoke(wrap); doCall(wrap); }
也就是說,ajax 函數的一個更容易理解的寫法是
fun ajax(init: HttpRequestWrapper.() -> Unit) { val wrap = HttpRequestWrapper() init.invoke(wrap) doCall(wrap) }
咱們在實際應用的時候,能夠直接把 init 寫成Lambda 表達式的形式,由於接收者類型HttpRequestWrapper 能夠從上下文推斷出來。
咱們這樣調用 ajax 函數:
ajax { url = testUrl method = "get" success { string -> println(string) Assert.assertTrue(string.contains("百度一下")) } error { e -> println(e.message) } }
下面是幾個測試代碼示例:
package com.kotlin.easy import com.alibaba.fastjson.JSONObject import okhttp3.MediaType import okhttp3.RequestBody import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 /** * Created by jack on 2017/7/23. */ @RunWith(JUnit4::class) class KAjaxTest { @Test fun testHttpOnSuccess() { val testUrl = "https://www.baidu.com" ajax { url = testUrl method = "get" success { string -> println(string) Assert.assertTrue(string.contains("百度一下")) } error { e -> println(e.message) } } } @Test fun testHttpOnError() { val testUrl = "https://www2.baidu.com" ajax { url = testUrl method = "get" success { string -> println(string) } error { e -> println(e.message) Assert.assertTrue("connect timed out" == e.message) } } } @Test fun testHttpPost() { var json = JSONObject() json.put("name", "Kotlin DSL Http") json.put("owner", "Kotlin") val postBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json.toString()) ajax { url = "saveArticle" method = "post" body = postBody success { string -> println(string) } error { e -> println(e.message) } } } @Test fun testLambda() { val testUrl = "https://www.baidu.com" val init: HttpRequestWrapper.() -> Unit = { this.url = testUrl this.method = "get" this.success { string -> println(string) Assert.assertTrue(string.contains("百度一下")) } this.error { e -> println(e.message) } } ajax(init) }
到這裏,咱們已經完成了一個極簡的 Kotlin Ajax DSL。
本節工程源碼: https://github.com/EasyKotlin...
相比於Java,Kotlin對函數式編程的支持更加友好。Kotlin 的擴展函數和高階函數(Lambda 表達式),爲定義Kotlin DSL提供了核心的特性支持。
使用DSL的代碼風格,可讓咱們的程序更加直觀易懂、簡潔優雅。若是使用Kotlin來開發項目的話,咱們徹底能夠去嘗試一下。
點擊這裏 > 去京東商城購買閱讀
點擊這裏 > 去天貓商城購買閱讀