《Kotlin 反应式编程》使用 RxKotlin 实现一个极简

作者: 光剑书架上的书 | 来源:发表于2018-04-23 11:10 被阅读101次

    《Kotlin 反应式编程》使用 RxKotlin 实现一个极简的 http DSL Reactive Programming Using Rx Kotlin

    https://github.com/ReactiveX/RxKotlin

    RxKotlin: RxJava bindings for Kotlin

    使用 RxKotlin 实现一个极简的 http DSL ( Reactive Programming Using Rx Kotlin )

    我们现在已经基本知道 Kotlin 中 DSL 的样子了。但是这些 DSL 都是怎样实现的呢?本节我们就通过实现一个极简的http DSL来学习创建 DSL 背后的基本原理。

    在这里我们对 OkHttp 做一下简单的封装,实现一个类似 jquery 中的 Ajax 的 http 请求的DSL。

    OkHttp 是一个成熟且强大的网络库,在Android源码中已经使用OkHttp替代原先的HttpURLConnection。很多著名的框架例如Picasso、Retrofit也使用OkHttp作为底层框架。

    提示: 更多关于OkHttp 的使用可参考: http://square.github.io/okhttp/

    创建 Kotlin Gradle 项目

    我们首先使用 IDEA 创建 Kotlin Gradle 项目

    螢幕快照 2017-07-23 18.43.04.png

    然后,在 build.gradle 里面配置依赖

        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' }
        ...
    }
    

    RxKotlin

    ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展,由微软的架构师Erik Meijer领导的团队开发,在2012年11月开源。

    Rx扩展了观察者模式用于支持数据和事件序列。Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步I/O(非阻塞)数据流。

    Rx库支持.NET、JavaScript和C++ 。Rx近几年越来越流行,现在已经支持几乎全部的流行编程语言了。一个语言列表如下所示:

    Rx 支持的编程语言 项目主页
    Java RxJavahttps://github.com/ReactiveX/RxJava
    JavaScript RxJShttps://github.com/ReactiveX/rxjs
    C# Rx.NEThttps://github.com/Reactive-Extensions/Rx.NET
    C#(Unity) UniRxhttps://github.com/neuecc/UniRx
    Scala RxScalahttps://github.com/ReactiveX/RxScala
    Clojure RxClojurehttps://github.com/ReactiveX/RxClojure
    C++ RxCpphttps://github.com/Reactive-Extensions/RxCpp
    Lua RxLuahttps://github.com/bjornbytes/RxLua
    Ruby Rx.rbhttps://github.com/Reactive-Extensions/Rx.rb
    Python: RxPYhttps://github.com/ReactiveX/RxPY
    Go RxGohttps://github.com/ReactiveX/RxGo
    Groovy RxGroovyhttps://github.com/ReactiveX/RxGroovy
    JRuby RxJRubyhttps://github.com/ReactiveX/RxJRuby
    Kotlin RxKotlinhttps://github.com/ReactiveX/RxKotlin
    Swift RxSwifthttps://github.com/kzaher/RxSwift
    PHP RxPHPhttps://github.com/ReactiveX/RxPHP
    Elixir reaxivehttps://github.com/alfert/reaxive
    Dart RxDarthttps://github.com/ReactiveX/rxdart

    Rx的大部分语言库由ReactiveX这个组织负责维护。Rx 比较流行的库有RxJava/RxJS/Rx.NET等,当然未来RxKotlin也必将更加流行。

    提示: Rx 的社区网站是: http://reactivex.io/ 。 Github 地址:https://github.com/ReactiveX/

    Http请求对象封装类

    首先我们设计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) 请求失败回调函数

    http 执行引擎

    我们直接调用 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()处理请求方法的大小写的兼容。

    使用 RxKotlin 完成请求响应的异步处理

    我们首先新建一个数据发射源:一个可观察对象(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 发射数据。这个很像协程中的通道编程模型。

    DSL主函数 ajax

    我们的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/chatper14_kotlin_dsl_http

    本章小结

    相比于Java,Kotlin对函数式编程的支持更加友好。Kotlin 的扩展函数和高阶函数(Lambda 表达式),为定义Kotlin DSL提供了核心的特性支持。

    使用DSL的代码风格,可以让我们的程序更加直观易懂、简洁优雅。如果使用Kotlin来开发项目的话,我们完全可以去尝试一下。


    《Kotlin极简教程》正式上架:

    点击这里 > 去京东商城购买阅读

    点击这里 > 去天猫商城购买阅读

    非常感谢您亲爱的读者,大家请多支持!!!有任何问题,欢迎随时与我交流~


    相关文章

      网友评论

        本文标题:《Kotlin 反应式编程》使用 RxKotlin 实现一个极简

        本文链接:https://www.haomeiwen.com/subject/pimklftx.html