什么是函数响应式编程(Java&Android版本)

函数响应式编程(FRP)为解决现代编程问题提供了全新的视角。一旦理解它,可以极大地简化你的项目,特别是处理嵌套回调的异步事件,复杂的列表过滤和变换,或者时间相关问题。

我将尽量跳过对函数响应式编程学院式的解释(网络上已经有很多),并重点从实用的角度帮你理解什么是函数响应式编程,以及工作中怎么应用它。本文将围绕函数响应式编程的一个具体实现RxJava,它可用于Java和Android。

开始

我们以一个真实的例子来开始讲解函数响应式编程怎么提高我们代码的可读性。我们的任务是通过查询GitHub的API,首先获取用户列表,然后请求每个用户的详细信息。这个过程包括两个web 服务端点:

https://api.github.com/users - 获取用户列表;

https://api.github.com/users/{username} -获取特定用户的详细信息,例如https://api.github.com/users/mutexkid。

旧的风格

下面例子你可能已经很熟悉了:它调用web service,使用回调接口将成功的结果传递给下一个web service请求,同时定义另一个成功回调,然后发起下一个web service请求。你可以看到,这会导致两层嵌套的回调:

//The "Nested Callbacks" Way

public void fetchUserDetails() {

//first, request the users...

mService.requestUsers(new Callback() {

@Override

public void success(final GithubUsersResponse githubUsersResponse,

final Response response) {

Timber.i(TAG, "Request Users request completed");

final synchronized List githubUserDetails = new ArrayList();

//next, loop over each item in the response

for (GithubUserDetail githubUserDetail : githubUsersResponse) {

//request a detail object for that user

mService.requestUserDetails(githubUserDetail.mLogin,

new Callback() {

@Override

public void success(GithubUserDetail githubUserDetail,

Response response) {

Log.i("User Detail request completed for user : " + githubUserDetail.mLogin);

githubUserDetails.add(githubUserDetail);

if (githubUserDetails.size() == githubUsersResponse.mGithubUsers.size()) {

//we‘ve downloaded‘em all - notify all who are interested!

mBus.post(new UserDetailsLoadedCompleteEvent(githubUserDetails));

}

}

@Override

public void failure(RetrofitError error) {

Log.e(TAG, "Request User Detail Failed!!!!", error);

}

});

}

}

@Override

public void failure(RetrofitError error) {

Log.e(TAG, "Request User Failed!!!!", error);

}

});

}

尽管这不是最差的代码-至少它是异步的,因此在等待每个请求完成的时候不会阻塞-但由于代码复杂(增加更多层次的回调代码复杂度将呈指数级增长)因此远非理想的代码。当我们不可避免要修改代码时(在前面的web service调用中,我们依赖前一次的回调状态,因此它不适用于模块化或者修改要传递给下一个回调的数据)也远非容易的工作。我们亲切的称这种情况为“回调地狱”。

RxJava的方式

下面让我们看看使用RxJava如何实现相同的功能:

public void rxFetchUserDetails() {

//request the users

mService.rxRequestUsers().concatMap(Observable::from)

.concatMap((GithubUser githubUser) ->

//request the details for each user

mService.rxRequestUserDetails(githubUser.mLogin)

)

//accumulate them as a list

.toList()

//define which threads information will be passed on

.subscribeOn(Schedulers.newThread())

.observeOn(AndroidSchedulers.mainThread())

//post them on an eventbus

.subscribe(githubUserDetails -> {

EventBus.getDefault().post(new UserDetailsLoadedCompleteEvent(githubUserDetails));

});

}

如你所见,使用函数响应式编程模型我们完全摆脱了回调,并最终得到了更短小的程序。让我们从函数响应式编程的基本定义开始慢慢解释到底发生了什么,并逐渐理解上面的代码,这些代码托管在GitHub上面。

从根本上讲,函数响应式编程是在观察者模式的基础上,增加对Observables发送的数据流进行操纵和变换的功能。在上面的例子中,Observables是我们的数据流通所在的管道。

回顾一下,观察者模式包含两个角色:一个Observable和一个或者多个Observers。Observable发送事件,而Observer订阅和接收这些事件。在上面的例子中,.subscribe()函数用于给Observable添加一个Observer,并创建一个请求。

构建一个Observable管道

对Observable管道的每个操作都将返回一个新的Observable,这个新的Observable的内容要么和操作前相同,要么就是经过转换的。这种方式使得我们可以对任务进行分解,并把事件流分解成小的操作,接着把这些Observables拼接起来从而完成更复杂的行为或者重用管道中的每个独立的单元。我们对Observable的每个方法调用会被加入到总的管道中以便我们的数据在其中流动。

下面首先让我们从搭建一个Observable开始,来看一个具体的例子:

Observable sentenceObservable = Observable.from(“this”, “is”, “a”, “sentence”);

这样我们就定义好了管道的第一个部分:Observable。在其中流通的数据是一个字符串序列。首先要认识到的是这是没有实现任何功能的非阻塞代码,仅仅定义了我们想要完成什么事情。Observable只有在我们“订阅”它之后才会开始工作,也就是说给它注册一个Observer之后。

Observable.subscribe(new Action1() {

@Override

public void call(String s) {

System.out.println(s);

}

});

到这一步Observable才会发送由每个独立的Observable的from()函数添加的数据块。管道会持续发送Observables直到所有Observables都被处理完成。

变换数据流

现在我们得到正在发送的字符串流,我们可以按照需要对这些数据流进行变换,并建立更复杂的行为。

Observable sentenceObservable = Observable.from(“this”, “is”, “a”, “sentence”);

sentenceObservable.map(new Func1() {

@Override

public String call(String s) {

return s.toUpperCase() + " ";

}

})

.toList()

.map(new Func1, String>() {

@Override

public String call(List strings) {

Collections.reverse(strings);

return strings.toString();

}

})

//subscribe to the stream of Observables

.subscribe(new Action1() {

@Override

public void call(String s) {

System.out.println(s);

}

});

一旦Observable被订阅了,我们会得到“SENTENCE A IS THIS”。上面调用的.map函数接受Func1类的对象,该类有两个范型类型参数:一个是输入类型(前一个Observable的内容),另一个是输出类型(在这个例子中,是一个经过大写转换,格式化并用一个新的Observable实例包装的字符串,最终被传递给下一个函数)。如你所见,我们通过可重用的管道组合实现更复杂的功能。

上面的例子中,我们还可以使用Java8的lambda表达式来进一步简化代码:

Observable.just("this", "is", "a", "sentence").map(s -> s.toUpperCase() + " ").toList().map(strings -> {

Collections.reverse(strings);

return strings.toString();

});

在subscribe函数中,我们传递Action1类对象作为参数,并以String类型作为范型参数。这定义了订阅者的行为,当被观察者发送最后一个事件后,处理后的字符串就被接收到了。这是.subscribe()函数最简单的重载形式(参见https://github.com/ReactiveX/RxJava/wiki/Observable#establishing-subscribers可以看到更复杂的函数重载签名)。

这个例子展示了变换函数.map()和聚合函数.toList(),在操纵数据流的能力方面这仅仅是冰山一角(所有可用的数据流操作函数可见https://github.com/ReactiveX/RxJava/wiki),但它显示了基本概念:在函数响应式编程中,我们可以通过实现了数据转换或者数据操作功能的管道中独立的单元来转换数据流。根据需要我们可以在其他由Observables组成的管道复用这些独立的单元。通过把这些Observable单元拼接在一起,我们可以组成更复杂的特性,但同时保持它们作为易于理解和可修改的可组合逻辑小单元。

使用Scheduler管理线程

在web service例子中,我们展示了如何使用RxJava发起网络请求。我们谈及转换,聚合和订阅Observable数据流,但我们没有谈及Observable数据流的web请求是怎样实现异步的。

这就属于FRP编程模型如何调用Scheduler的范畴了-该策略定义了Observable流事件在哪个线程中发生,以及订阅者在哪个线程消费Observable的处理结果。在web service例子中,我们希望请求在后台线程中进行,而订阅行为发生在主线程中,因此我们如下定义:

.subscribeOn(Schedulers.newThread())

.observeOn(AndroidSchedulers.mainThread())

//post them on an eventbus

.subscribe(githubUserDetails -> {

EventBus.getDefault().post(new UserDetailsLoadedCompleteEvent(githubUserDetails));

});

Observable.subscribeOn(Scheduler scheduler)函数指定Observable的工作需要在指定的Scheduler线程中执行。Observable.observeOn(Scheduler scheduler)指定Observable在哪个Scheduler线程触发订阅者们的onNext(),onCompleted(),和onError()函数,并调用Observable的observeOn()函数,传递正确的Scheduler给它。

下面是可能会用到Scheduler:

Schedulers.computation():用于计算型工作例如事件循环和回调处理,不要在I/O中使用这个函数(应该使用Schedulers.io()函数);

Schedulers.from(executor):使用指定的Executor作为Scheduler;

Schedulers.immediate():在当前线程中立即开始执行任务;

Schedulers.io():用于I/O密集型工作例如阻塞I/O的异步操作,这个调度器由一个会随需增长的线程池支持;对于一般的计算工作,使用Schedulers.computation();

Schedulers.newThread():为每个工作单元创建一个新的线程;

Schedulers.test():用于测试目的,支持单元测试的高级事件;

Schedulers.trampoline():在当前线程中的工作放入队列中排队,并依次操作。

通过设置observeOn和subscribeOn调度器,我们定义了网络请求使用哪个线程(Schedulers.newThread())。

下一步

我们已经在本文中涵盖了很多基础内容,到这里你应该对函数响应式编程如何工作有了很好的认识。请查看并理解本文介绍的工程,它托管在GitHub上面,阅读RxJava文档并检出rxjava-koans工程,以测试驱动的方式掌握函数响应式编程范型。

Android什么是函数,什么是函数响应式编程(JavaAndroid版本)相关推荐

  1. Kotlin Flow响应式编程,基础知识入门

    Kotlin在推出多年之后已经变得非常普及了.相信现在至少有80%的Android项目已经在使用Kotlin开发,或者有部分功能使用Kotlin开发. 关于Kotlin方面的知识,我其实分享的文章并不 ...

  2. Kotlin Flow响应式编程,操作符函数进阶

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新. 大家好,今天原创. 在上一篇原创文章当中,我跟大家说了会开启一个新的系列,讲一讲Kotlin ...

  3. Rxswift学习之(一)函数响应式编程思想

    Rxswift学习之(一)函数响应式编程思想 1. 函数响应式编程思想必备基本概念简介 2. iOS中三种编程思想:链式.函数式和响应式编程 2.1 链式编程 2.2 函数式编程 2.3 响应式编程 ...

  4. 【iOS架构】iOS ReactiveCocoa函数响应式编程

    声明式编程 声明式编程(declarative programming)是一种编程范型,与命令式编程相对立.它描述目标的性质,让电脑明白目标,而非流程.声明式编程不用告诉电脑问题领域,从而避免随之而来 ...

  5. 什么是函数响应式编程?

    什么是函数响应式编程? 响应式编程思想为体,函数式编程思想为用.首先我们要来了解什么是函数式编程和响应式编程. 什么是函数式编程? 顾名思义,函数式编程就是用函数来解决问题的编程方式,几乎任何一门语言 ...

  6. 响应式编程在Android中的应用

    响应式编程简介 响应式编程是一种基于异步数据流概念的编程模式.数据流就像一条河:它可以被观测,被过滤,被操作,或者为新的消费者与另外一条流合并为一条新的流. 响应式编程的一个关键概念是事件.事件可以被 ...

  7. Android开发之ViewPager+ActionBar+Fragment实现响应式可滑动Tab

    今天我们要实现的这个效果呢,在Android的应用中十分地常见,我们可以看到下面两张图,无论是系统内置的联系人应用,还是AnyView的阅读器应用,我们总能找到这样的影子,当我们滑动屏幕时,Tab可以 ...

  8. 响应式编程在Android 中的一些探索

    响应式在前端领域已经变得十分流行,很多主流框架都采用响应式来进行页面的展示刷新.本文主要是探索一下响应式在移动端Android上的一些实践,包括对响应式思想的理解,以及目前Android上实现响应式的 ...

  9. Android【Retrofit(HTTP客户端),RxJAVA(响应式编程)】

    1 Retrofit(HTTP客户端) 1.1 简介 我们项目当中的每个app都需要用到网络和服务器进行交互,在Android项目开发中使用HTTP协议完成通信的话,基本上都要用到OkHttp或者Re ...

最新文章

  1. 【3月30日直播】新冠病毒全基因组测序——Midnight试剂盒及整体解决方案
  2. Stream Processing: Apache Kafka的Exactly-once的定义 原理和实现
  3. 全网最详系列教程-nacos配置中心详解-NameSpace、Group、DataID
  4. 山西上党残疾男子“只”手脱贫 带领村民增收
  5. 小程序设置页面背景颜色
  6. [论文翻译] iCaRL: Incremental Classifier and Representation Learning
  7. 你们都用什么反编译工具?
  8. C语言实例:三个数从小到大排序
  9. 华为U9508一键root
  10. 0CTF 2016 RSA?(未完成)
  11. 制作QQ背景音乐链接
  12. [转载]样式表编写效率手册 [ 日期:2004-10-15 ] [ 来自:Plod ]
  13. 【排列问题】-全排列
  14. 河南高校计算机好的是排名,河南高校的计算机科学与技术专业排名如何?
  15. 给新手学习MySQL的建议
  16. Hdoj 2036.改革春风吹满地 题解
  17. 价值为纲:华为财经管理
  18. 微信共享充电桩小程序系统设计与实现
  19. Plupload上传文件
  20. uart接口介绍和认识

热门文章

  1. Java传值与传引用
  2. android错误-android.util.AndroidRuntimeException:You cannot combine custom titles with other title
  3. 2018百度之星度度熊学队列
  4. 算法练习day15——190403(简介、求n!、汉诺塔、打印字符串的子序列、打印字符串的全排列、母牛生小牛、最小路径和、累加和是否达到给定值)
  5. IO-5(InputStreamReader、OutputStreamWriter、序列化流、反序列化流、Serializable、transient)
  6. 小明分享:SSD201/202系统烧录篇,多种烧录方式,小明都做了分享,有需要的小伙伴拿走不谢哈!更多资料需求可以关注我,我是启明云端的小明MM!
  7. python queue 调试_python:如何创建用于调试的持久内存结构
  8. 已经无法合并还报请合并git_Git不能自动合并怎么解决?
  9. vi的插入模式下退格和方向键不能使用的解决方法
  10. linux重启终端后go命令,Linux基础命令之关机,重启,注销-Go语言中文社区