如何让plex支持dff格式转换

&p&本文首发于微信公众号 『玉刚说』,原文链接:&a href=&/?target=https%3A//mp./s%3F__biz%3DMzIwMTAzMTMxMg%3D%3D%26mid%3D%26idx%3D1%26sn%3D49cb619fb5b7c3b8f894e8%26chksm%3D8eec808db99b099b6b0bc5e983fc10df48a085a78caec9d76bcfrd& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Gradle从入门到实战 - Groovy基础&i class=&icon-external&&&/i&&/a&&/p&&h2&前言&/h2&&p&Android方向的第一期文章,会专注于Gradle系列,名字叫做『 Gradle从入门到实战』,计划有如下几个课程:&/p&&ul&&li&Groovy基础&/li&&li&全面理解Gradle&/li&&li&如何创建Gradle插件&/li&&li&分析Android的build tools插件&/li&&li&实战,从0到1完成一款Gradle插件&/li&&/ul&&p&本篇文章讲解Groovy基础。为什么是Groovy基础呢,因为玩转Gradle并不需要学习Groovy的全部细节。Groovy是一门jvm语言,功能比较强大,细节也很多,全部学习的话比较耗时,对我们来说收益较小。&/p&&h2&为什么是Gradle?&/h2&&p&Gradle是目前Android主流的构建工具,不管你是通过命令行还是通过AndroidStudio来build,最终都是通过Gradle来实现的。所以学习Gradle非常重要。&/p&&p&目前国内对Android领域的探索已经越来越深,不少技术领域如插件化、热修复、构建系统等都对Gradle有迫切的需求,不懂Gradle将无法完成上述事情。所以Gradle必须要学习。&/p&&h2&如何学习Gradle?&/h2&&p&大部分人对Gradle表示一脸懵逼,每当遇到一个问题的时候都需要从网上去查,这是一个误区。&/p&&p&Gradle不单单是一个配置脚本,它的背后是几门语言,如果硬让我说,我认为是三门语言。&/p&&ul&&li&Groovy Language&/li&&li&Gradle DSL&/li&&li&Android DSL&/li&&/ul&&p&DSL的全称是Domain Specific Language,即领域特定语言,或者直接翻译成“特定领域的语言”,算了,再直接点,其实就是这个语言不通用,只能用于特定的某个领域,俗称“小语言”。因此DSL也是语言。&/p&&p&在你不懂这三门语言的情况下,你很难达到精通Gradle的程度。这个时候从网上搜索,或者自己记忆的一些配置,其实对你来说是很大的负担。但是把它们当做语言来学习,则不需要记忆这些配置,因为语言都是有文档的,我们只需要学语法然后查文档即可,没错,这就是学习方法,这就是正道。&/p&&p&你需要做什么呢?跟着我学习就行啦!下面步入正题,让我们来开始学习Groovy的基本语法。&/p&&h2&Groovy和Java的关系&/h2&&p&Groovy是一门jvm语言,它最终是要编译成class文件然后在jvm上执行,所以Java语言的特性Groovy都支持,我们完全可以混写Java和Groovy。&br&&/p&&p&既然如此,那Groovy的优势是什么呢?简单来说,Groovy提供了更加灵活简单的语法,大量的语法糖以及闭包特性可以让你用更少的代码来实现和Java同样的功能。比如解析xml文件,Groovy就非常方便,只需要几行代码就能搞定,而如果用Java则需要几十行代码。&/p&&h2&Groovy的变量和方法声明&/h2&&p&在Groovy中,通过 def 关键字来声明变量和方法,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def a = 1;
def b = &hello world&;
def int c = 1;
def hello() {
println (&hello world&);
&/code&&/pre&&/div&&p&在Groovy中,很多东西都是可以省略的,比如&/p&&ul&&li&语句后面的分号是可以省略的&/li&&li&变量的类型和方法的返回值也是可以省略的&/li&&li&方法调用时,括号也是可以省略的&/li&&li&甚至语句中的return都是可以省略的&/li&&/ul&&p&所以上面的代码也可以写成如下形式:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def a = 1
def b = &hello world&
def int c = 1
def hello() {
println &hello world& // 方法调用省略括号
// 方法返回值省略return
def hello(String msg) {
println (msg)
// 方法省略参数类型
int hello(msg) {
println (msg)
// 方法省略参数类型
int hello(msg) {
println msg
return 1 // 这个return不能省略
println &done&
&/code&&/pre&&/div&&p&&b&总结&/b&&/p&&ul&&li&在Groovy中,类型是弱化的,所有的类型都可以动态推断,但是Groovy仍然是强类型的语言,类型不匹配仍然会报错;&/li&&li&在Groovy中很多东西都可以省略,所以寻找一种自己喜欢的写法;&/li&&li&Groovy中的注释和Java中相同。&/li&&/ul&&h2&Groovy的数据类型&/h2&&p&在Groovy中,数据类型有:&/p&&ul&&li&Java中的基本数据类型&/li&&li&Java中的对象&/li&&li&Closure(闭包)&/li&&li&加强的List、Map等集合类型&/li&&li&加强的File、Stream等IO类型&/li&&/ul&&p&类型可以显示声明,也可以用 def 来声明,用 def 声明的类型Groovy将会进行类型推断。&/p&&p&基本数据类型和对象这里不再多说,和Java中的一致,只不过在Gradle中,对象默认的修饰符为public。下面主要说下String、闭包、集合和IO等。&/p&&p&&b&1. String&/b&&/p&&p&String的特色在于字符串的拼接,比如&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def a = 1
def b = &hello&
def c = &a=${a}, b=${b}&
a=1, b=hello
&/code&&/pre&&/div&&p&&b&2. 闭包&/b&&/p&&p&Groovy中有一种特殊的类型,叫做Closure,翻译过来就是闭包,这是一种类似于C语言中函数指针的东西。闭包用起来非常方便,在Groovy中,闭包作为一种特殊的数据类型而存在,闭包可以作为方法的参数和返回值,也可以作为一个变量而存在。&/p&&p&如何声明闭包?&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&{ parameters -&
&/code&&/pre&&/div&&p&闭包可以有返回值和参数,当然也可以没有。下面是几个具体的例子:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def closure = { int a, String b -&
println &a=${a}, b=${b}, I am a closure!&
// 这里省略了闭包的参数类型
def test = { a, b -&
println &a=${a}, b=${b}, I am a closure!&
def ryg = { a, b -&
closure(100, &renyugang&)
test.call(100, 200)
def c = ryg(100,200)
&/code&&/pre&&/div&&p&闭包可以当做函数一样使用,在上面的例子中,将会得到如下输出:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&a=100, b=renyugang, I am a closure!
a=100, b=200, I am a closure!
&/code&&/pre&&/div&&p&另外,如果闭包不指定参数,那么它会有一个隐含的参数 it&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 这里省略了闭包的参数类型
def test = {
println &find ${it}, I am a closure!&
find 100, I am a closure!
&/code&&/pre&&/div&&p&闭包的一个难题是如何确定闭包的参数,尤其当我们调用Groovy的API时,这个时候没有其他办法,只有查询Groovy的文档:&/p&&p&&a href=&/?target=http%3A//www.groovy-lang.org/api.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&groovy-lang.org/api.htm&/span&&span class=&invisible&&l&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&&a href=&/?target=http%3A//docs.groovy-lang.org/latest/html/groovy-jdk/index-all.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&docs.groovy-lang.org/la&/span&&span class=&invisible&&test/html/groovy-jdk/index-all.html&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&下面会结合具体的例子来说明如何查文档。&/p&&p&&b&3. List和Map&/b&&/p&&p&Groovy加强了Java中的集合类,比如List、Map、Set等。&/p&&p&List的使用如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def emptyList = []
def test = [100, &hello&, true]
test[1] = &world&
println test[0]
println test[1]
test && 200
println test.size
&/code&&/pre&&/div&&p&List还有一种看起来很奇怪的操作符&&,其实这并没有什么大不了,左移位表示向List中添加新元素的意思,这一点从文档当也能查到。&/p&&img src=&/v2-2b84b58fce7d08d42b86e3524dff003e_b.png& data-rawwidth=&640& data-rawheight=&366& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&/v2-2b84b58fce7d08d42b86e3524dff003e_r.png&&&p&&br&&/p&&p&其实Map也有左移操作,这如果不查文档,将会非常费解。&/p&&p&Map的使用如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def emptyMap = [:]
def test = [&id&:1, &name&:&renyugang&, &isMale&:true]
test[&id&] = 2
test.id = 900
println test.id
println test.isMale
&/code&&/pre&&/div&&p&可以看到,通过Groovy来操作List和Map显然比Java简单的多。&/p&&p&这里借助Map再讲述下如何确定闭包的参数。比如我们想遍历一个Map,我们想采用Groovy的方式,通过查看文档,发现它有如下两个方法,看起来和遍历有关:&/p&&img src=&/v2-7fa7aa6b81b65b64130d_b.png& data-rawwidth=&640& data-rawheight=&211& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&/v2-7fa7aa6b81b65b64130d_r.png&&&p&可以发现,这两个each方法的参数都是一个闭包,那么我们如何知道闭包的参数呢?当然不能靠猜,还是要查文档。&/p&&img src=&/v2-ff3ca938dc5ee037db7fc_b.png& data-rawwidth=&640& data-rawheight=&690& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&/v2-ff3ca938dc5ee037db7fc_r.png&&&p&&br&&/p&&p&通过文档可以发现,这个闭包的参数还是不确定的,如果我们传递的闭包是一个参数,那么它就把entry作为参数;如果我们传递的闭包是2个参数,那么它就把key和value作为参数。&/p&&p&按照这种提示,我们来尝试遍历下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def emptyMap = [:]
def test = [&id&:1, &name&:&renyugang&, &isMale&:true]
test.each { key, value -&
println &two parameters, find [${key} : ${value}]&
test.each {
println &one parameters, find [${it.key} : ${it.value}]&
two parameters, find [id : 1]
two parameters, find [name : renyugang]
two parameters, find [isMale : true]
one parameters, find [id : 1]
one parameters, find [name : renyugang]
one parameters, find [isMale : true]
&/code&&/pre&&/div&&p&另外一个eachWithIndex方法教给大家练习,自己查文档,然后尝试用这个方法去遍历。&/p&&p&试想一下,如果你不知道查文档,你又怎么知道each方法如何使用呢?光靠从网上搜,API文档中那么多接口,搜的过来吗?记得住吗?&/p&&p&&b&4. 加强的IO&/b& &/p&&p&在Groovy中,文件访问要比Java简单的多,不管是普通文件还是xml文件。怎么使用呢?还是来查文档。&/p&&img src=&/v2-e0f9ceb2c9c1b30eb9a6_b.png& data-rawwidth=&640& data-rawheight=&447& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&/v2-e0f9ceb2c9c1b30eb9a6_r.png&&&p&&br&&/p&&p&根据File的eachLine方法,我们可以写出如下遍历代码,可以看到,eachLine方法也是支持1个或2个参数的,这两个参数分别是什么意思,就需要我们学会读文档了,一味地从网上搜例子,多累啊,而且很难彻底掌握:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def file = new File(&a.txt&)
println &read file using two parameters&
file.eachLine { line, lineNo -&
println &${lineNo} ${line}&
println &read file using one parameters&
file.eachLine { line -&
println &${line}&
read file using two parameters
read file using one parameters
&/code&&/pre&&/div&&p&除了eachLine,File还提供了很多Java所没有的方法,大家需要浏览下大概有哪些方法,然后需要用的时候再去查就行了,这就是学习Groovy的正道。&/p&&p&下面我们再来看看访问xml文件,也是比Java中简单多了。&br&Groovy访问xml有两个类:XmlParser和XmlSlurper,二者几乎一样,在性能上有细微的差别,如果大家感兴趣可以从文档上去了解细节,不过这对于本文不重要。&/p&&p&在下面的链接中找到XmlParser的API文档,参照例子即可编程,&/p&&p&&a href=&/?target=http%3A//docs.groovy-lang.org/docs/latest/html/api/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&docs.groovy-lang.org/do&/span&&span class=&invisible&&cs/latest/html/api/&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&。&/p&&p&假设我们有一个xml,attrs.xml,如下所示:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&resources&
&declare-styleable name=&CircleView&&
&attr name=&circle_color& format=&color&&#98ff02&/attr&
&attr name=&circle_size& format=&integer&&100&/attr&
&attr name=&circle_title& format=&string&&renyugang&/attr&
&/declare-styleable&
&/resources&
&/code&&/pre&&/div&&p&那么如何遍历它呢?&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def xml = new XmlParser().parse(new File(&attrs.xml&))
// 访问declare-styleable节点的name属性
println xml['declare-styleable'].@name[0]
// 访问declare-styleable的第三个子节点的内容
println xml['declare-styleable'].attr[2].text()
CircleView
&/code&&/pre&&/div&&p&更多的细节都可以从我发的那个链接中查到,大家有需要查文档即可。&/p&&p&Groovy的其他特性&/p&&p&除了本文中已经分析的特性外,Groovy还有其他特性。&/p&&ul&&li&&b&Class是一等公民&/b& &/li&&/ul&&p&在Groovy中,所有的Class类型,都可以省略.class,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&func(File.class)
func(File)
def func(Class clazz) {
&/code&&/pre&&/div&&ul&&li&&b&Getter和Setter&/b& &/li&&/ul&&p&在Groovy中,Getter/Setter和属性是默认关联的,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&class Book {
private String name
String getName() { return name }
void setName(String name) { this.name = name }
class Book {
String name
&/code&&/pre&&/div&&p&上述两个类完全一致,只有有属性就有Getter/Setter;同理,只要有Getter/Setter,那么它就有隐含属性。&/p&&ul&&li&&b&with操作符&/b& &/li&&/ul&&p&在Groovy中,当对同一个对象进行操作时,可以使用with,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Book bk = new Book()
bk.name = &android art&
bk.press = &china press&
可以简写为:
Book bk = new Book()
name = &android art&
press = &china press&
&/code&&/pre&&/div&&ul&&li&&b&判断是否为真&/b& &/li&&/ul&&p&在Groovy中,判断是否为真可以更简洁:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if (name != null && name.length & 0) {}
可以替换为:
if (name) {}
&/code&&/pre&&/div&&ul&&li&&b&简洁的三元表达式&/b& &/li&&/ul&&p&在Groovy中,三元表达式可以更加简洁,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def result = name != null ? name : &Unknown&
// 省略了name
def result = name ?: &Unknown&
&/code&&/pre&&/div&&ul&&li&&b&简洁的非空判断&/b& &/li&&/ul&&p&在Groovy中,非空判断可以用?表达式,比如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if (order != null) {
if (order.getCustomer() != null) {
if (order.getCustomer().getAddress() != null) {
System.out.println(order.getCustomer().getAddress());
可以简写为:
println order?.customer?.address
&/code&&/pre&&/div&&ul&&li&&b&使用断言&/b& &/li&&/ul&&p&在Groovy中,可以使用assert来设置断言,当断言的条件为false时,程序将会抛出异常:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def check(String name) {
// name non-null and non-empty according to Gro
assert name
// safe navigation + Groovy Truth to check
assert name?.size() & 3
&/code&&/pre&&/div&&ul&&li&&b&switch方法&/b& &/li&&/ul&&p&在Groovy中,switch方法变得更加灵活,可以同时支持更多的参数类型:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def x = 1.23
def result = &&
switch (x) {
case &foo&: result = &found foo&
// lets fall through
case &bar&: result += &bar&
case [4, 5, 6, 'inList']: result = &list&
case 12..30: result = &range&
case Integer: result = &integer&
case Number: result = &number&
case { it & 3 }: result = &number & 3&
default: result = &default&
assert result == &number&
&/code&&/pre&&/div&&ul&&li&&b&==和equals&/b& &/li&&/ul&&p&在Groovy中,==相当于Java的equals,,如果需要比较对个对象是否是同一个,需要使用.is()。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Object a = new Object()
Object b = a.clone()
assert a == b
assert !a.is(b)
&/code&&/pre&&/div&&p&本小节参考了如下文章,十分感谢原作者的付出:&/p&&p&1. &a href=&/?target=http%3A///p/ba55dc163dfd& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/p/ba55dc163&/span&&span class=&invisible&&dfd&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&h2&编译、运行Groovy&/h2&&p&可以安装Groovy sdk来编译和运行。但是我并不想搞那么麻烦,毕竟我们的最终目的只是学习Gradle。&/p&&p&推荐大家通过这种方式来编译和运行Groovy。&/p&&p&在当面目录下创建build.gradle文件,在里面创建一个task,然后在task中编写Groovy代码即可,如下所示:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&task(yugangshuo).doLast {
println &start execute yuangshuo&
def haveFun() {
println &have fun!&
System.out.println(&have fun!&);
def file1 = new File(&a.txt&)
def file2 = new File(&a.txt&)
assert file1 == file2
assert !file1.is(file2)
class Book {
private String name
String getName() { return name }
void setName(String name) { this.name = name }
&/code&&/pre&&/div&&p&只需要在haveFun方法中编写Groovy代码即可,如下命令即可运行:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&gradle yugangshuo
&/code&&/pre&&/div&&p&&/p&
本文首发于微信公众号 『玉刚说』,原文链接:前言Android方向的第一期文章,会专注于Gradle系列,名字叫做『 Gradle从入门到实战』,计划有如下几个课程:Groovy基础全面理解Gradle如何创建Gradle插件分析Android的build t…
&img src=&/50/v2-c1ed5f6aee6fa28af50fdb_b.jpg& data-rawwidth=&792& data-rawheight=&446& class=&origin_image zh-lightbox-thumb& width=&792& data-original=&/50/v2-c1ed5f6aee6fa28af50fdb_r.jpg&&&p&数据库是应用开发中常用的技术,在Android应用中也不例外。Android默认使用了SQLite数据库,在应用程序开发中,我们使用最多的无外乎增删改查。纵使操作简单,也有可能出现查找数据缓慢,插入数据耗时等情况,如果出现了这种问题,我们就需要考虑对数据库操作进行优化了。本文将介绍一些实用的数据库优化操作,希望可以帮助大家更好地在开发过程中使用数据库。&/p&&h2&建立索引&/h2&&p&很多时候,我们都听说,想要查找快速就建立索引。这句话没错,数据表的索引类似于字典中的拼音索引或者部首索引。&/p&&h3&索引的解释&/h3&&p&重温一下我们小时候查字典的过程:&/p&&ul&&li&对于已经知道拼音的字,比如中这个字,我们只需要在拼音索引里面找到zhong,就可以确定这个字在词典中的页码。&/li&&li&对于不知道拼音的字,比如欗这个字,我们只需要在部首索引里面查找这个字,就能找到确定这个字在词典中的页码。&/li&&/ul&&p&没错,索引做的事情就是这么简单,使得我们不需要查找整个数据表就可以实现快速访问。&/p&&h3&建立索引&/h3&&p&创建索引的基本语法如下&/p&&div class=&highlight&&&pre&&code class=&language-sql&&&span&&/span&&span class=&k&&CREATE&/span& &span class=&k&&INDEX&/span& &span class=&n&&index_name&/span& &span class=&k&&ON&/span& &span class=&k&&table_name&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&p&创建单列索引&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&CREATE INDEX index_name ON table_name (column_name);
&/code&&/pre&&/div&&h3&索引真的好么&/h3&&p&毋庸置疑,索引加速了我们检索数据表的速度。然而正如西方谚语 “There are two sides of a coin”,索引亦有缺点:&/p&&ul&&li&对于增加,更新和删除来说,使用了索引会变慢,比如你想要删除字典中的一个字,那么你同时也需要删除这个字在拼音索引和部首索引中的信息。&/li&&li&建立索引会增加数据库的大小,比如字典中的拼音索引和部首索引实际上是会增加字典的页数,让字典变厚的。&/li&&li&为数据量比较小的表建立索引,往往会事倍功半。&/li&&/ul&&p&所以使用索引需要考虑实际情况进行利弊权衡,对于查询操作量级较大,业务对要求查询要求较高的,还是推荐使用索引的。&/p&&h2&编译SQL语句&/h2&&p&SQLite想要执行操作,需要将程序中的sql语句编译成对应的SQLiteStatement,比如select * from record这一句,被执行100次就需要编译100次。对于批量处理插入或者更新的操作,我们可以使用显式编译来做到重用SQLiteStatement。&/p&&p&想要做到重用SQLiteStatement也比较简单,基本如下:&/p&&ul&&li&编译sql语句获得SQLiteStatement对象,参数使用?代替&/li&&li&在循环中对SQLiteStatement对象进行具体数据绑定,bind方法中的index从1开始,不是0&/li&&/ul&&p&请参考如下简单的使用代码&br&&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&insertWithPreCompiledStatement&/span&&span class=&o&&(&/span&&span class=&n&&SQLiteDatabase&/span& &span class=&n&&db&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&String&/span& &span class=&n&&sql&/span& &span class=&o&&=&/span& &span class=&s&&&INSERT INTO &&/span& &span class=&o&&+&/span& &span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&TABLE_RECORD&/span& &span class=&o&&+&/span& &span class=&s&&&( &&/span& &span class=&o&&+&/span& &span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&COLUMN_INSERT_TIME&/span& &span class=&o&&+&/span& &span class=&s&&&) VALUES(?)&&/span&&span class=&o&&;&/span&
&span class=&n&&SQLiteStatement&/span&
&span class=&n&&statement&/span& &span class=&o&&=&/span& &span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&compileStatement&/span&&span class=&o&&(&/span&&span class=&n&&sql&/span&&span class=&o&&);&/span&
&span class=&kt&&int&/span& &span class=&n&&count&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span&
&span class=&k&&while&/span& &span class=&o&&(&/span&&span class=&n&&count&/span& &span class=&o&&&&/span& &span class=&mi&&100&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&count&/span&&span class=&o&&++;&/span&
&span class=&n&&statement&/span&&span class=&o&&.&/span&&span class=&na&&clearBindings&/span&&span class=&o&&();&/span&
&span class=&n&&statement&/span&&span class=&o&&.&/span&&span class=&na&&bindLong&/span&&span class=&o&&(&/span&&span class=&mi&&1&/span&&span class=&o&&,&/span& &span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&currentTimeMillis&/span&&span class=&o&&());&/span&
&span class=&n&&statement&/span&&span class=&o&&.&/span&&span class=&na&&executeInsert&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&h2&显式使用事务&/h2&&p&在Android中,无论是使用SQLiteDatabase的insert,delete等方法还是execSQL都开启了事务,来确保每一次操作都具有原子性,使得结果要么是操作之后的正确结果,要么是操作之前的结果。&/p&&p&然而事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。既然属于文件,就符合Unix的文件范型(Open-Read/Write-Close),因而对于批量的修改操作会出现反复打开文件读写再关闭的操作。然而好在,我们可以显式使用事务,将批量的数据库更新带来的journal文件打开关闭降低到1次。&/p&&p&具体的实现代码如下:&br&&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&insertWithTransaction&/span&&span class=&o&&(&/span&&span class=&n&&SQLiteDatabase&/span& &span class=&n&&db&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&count&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span&
&span class=&n&&ContentValues&/span& &span class=&n&&values&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&ContentValues&/span&&span class=&o&&();&/span&
&span class=&k&&try&/span& &span class=&o&&{&/span&
&span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&beginTransaction&/span&&span class=&o&&();&/span&
&span class=&k&&while&/span& &span class=&o&&(&/span&&span class=&n&&count&/span&&span class=&o&&++&/span& &span class=&o&&&&/span& &span class=&mi&&100&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&values&/span&&span class=&o&&.&/span&&span class=&na&&put&/span&&span class=&o&&(&/span&&span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&COLUMN_INSERT_TIME&/span&&span class=&o&&,&/span& &span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&currentTimeMillis&/span&&span class=&o&&());&/span&
&span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&insert&/span&&span class=&o&&(&/span&&span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&TABLE_RECORD&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&n&&values&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&setTransactionSuccessful&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span& &span class=&k&&catch&/span& &span class=&o&&(&/span&&span class=&n&&Exception&/span& &span class=&n&&e&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&e&/span&&span class=&o&&.&/span&&span class=&na&&printStackTrace&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span& &span class=&k&&finally&/span& &span class=&o&&{&/span&
&span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&endTransaction&/span&&span class=&o&&();&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&上面的代码中,如果没有异常抛出,我们则认为事务成功,调用db.setTransactionSuccessful();确保操作真实生效。如果在此过程中出现异常,则批量数据一条也不会插入现有的表中。&/p&&h2&查询数据优化&/h2&&p&对于查询的优化,除了建立索引以外,有以下几点微优化的建议&/p&&h3&按需获取数据列信息&/h3&&p&通常情况下,我们处于自己省时省力的目的,对于查找使用类似这样的代码&br&&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&badQuery&/span&&span class=&o&&(&/span&&span class=&n&&SQLiteDatabase&/span& &span class=&n&&db&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&query&/span&&span class=&o&&(&/span&&span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&TABLE_RECORD&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&)&/span& &span class=&o&&;&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&其中上面方法的第二个参数类型为String[],意思是返回结果参考的colum信息,传递null表明需要获取全部的column数据。这里建议大家传递真实需要的字符串数据对象表明需要的列信息,这样做效率会有所提升。&/p&&h3&提前获取列索引&/h3&&p&当我们需要遍历cursor时,我们通常的做法是这样&br&&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span&&/span&&span class=&kd&&private&/span& &span class=&kt&&void&/span& &span class=&nf&&badQueryWithLoop&/span&&span class=&o&&(&/span&&span class=&n&&SQLiteDatabase&/span& &span class=&n&&db&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&n&&Cursor&/span& &span class=&n&&cursor&/span& &span class=&o&&=&/span& &span class=&n&&db&/span&&span class=&o&&.&/span&&span class=&na&&query&/span&&span class=&o&&(&/span&&span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&TABLE_RECORD&/span&&span class=&o&&,&/span& &span class=&k&&new&/span& &span class=&n&&String&/span&&span class=&o&&[]{&/span&&span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&COLUMN_INSERT_TIME&/span&&span class=&o&&},&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&,&/span& &span class=&kc&&null&/span&&span class=&o&&)&/span& &span class=&o&&;&/span&
&span class=&k&&while&/span& &span class=&o&&(&/span&&span class=&n&&cursor&/span&&span class=&o&&.&/span&&span class=&na&&moveToNext&/span&&span class=&o&&())&/span& &span class=&o&&{&/span&
&span class=&kt&&long&/span& &span class=&n&&insertTime&/span& &span class=&o&&=&/span& &span class=&n&&cursor&/span&&span class=&o&&.&/span&&span class=&na&&getLong&/span&&span class=&o&&(&/span&&span class=&n&&cursor&/span&&span class=&o&&.&/span&&span class=&na&&getColumnIndex&/span&&span class=&o&&(&/span&&span class=&n&&TableDefine&/span&&span class=&o&&.&/span&&span class=&na&&COLUMN_INSERT_TIME&/span&&span class=&o&&));&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&但是如果我们将获取ColumnIndex的操作提到循环之外,效果会更好一些,修改后的代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&private void goodQueryWithLoop(SQLiteDatabase db) {
Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
int insertTimeColumnIndex = cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME);
while (cursor.moveToNext()) {
long insertTime = cursor.getLong(insertTimeColumnIndex);
cursor.close();
&/code&&/pre&&/div&&h2&ContentValues的容量调整&/h2&&p&SQLiteDatabase提供了方便的ContentValues简化了我们处理列名与值的映射,ContentValues内部采用了HashMap来存储Key-Value数据,ContentValues的初始容量是8,如果当添加的数据超过8之前,则会进行双倍扩容操作,因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作。&/p&&h2&及时关闭Cursor&/h2&&p&使用数据库,比较常见的就是忘记关闭Cursor。关于如何发现未关闭的Cursor,我们可以使用StrictMode,详细请戳这里&a href=&/?target=http%3A///blog//android-tuning-tool-strictmode/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android性能调优利器StrictMode&i class=&icon-external&&&/i&&/a&&/p&&h2&耗时异步化&/h2&&p&数据库的操作,属于本地IO,通常比较耗时,如果处理不好,很容易导致&a href=&/?target=http%3A///blog//anr-in-android/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ANR&i class=&icon-external&&&/i&&/a&,因此建议将这些耗时操作放入异步线程中处理,这里推荐一个单线程 + 任务队列形式处理的&a href=&/?target=http%3A///blog//make-use-of-handlerthread/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HandlerThread&i class=&icon-external&&&/i&&/a&实现异步化。&/p&&h2&源码下载&/h2&&p&示例源码,存放在Github,地址为&a href=&/?target=https%3A///androidyue/AndroidSQLiteTuningDemo& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AndroidSQLiteTuningDemo&i class=&icon-external&&&/i&&/a&&/p&&h2&知乎 Live 推荐&/h2&&p&我将要在知乎上进行我的第二场 Live,题为《我学安卓的那些套路》,来分享我学习安卓的经验与心得。覆盖的内容如下:&/p&&ul&&li&Android 需要打好哪些编程基础?&/li&&li&除了编程基础,我们还需要补充哪些能力?&/li&&li&作为 Android 程序员,如何把握好技术的宽度和深度?&/li&&li&Android 每块知识学到什么程度,怎么做到?&/li&&li&如何从日常的工作中获取最大的收益?&/li&&li&Android 那么多库,我该选择哪些,怎么学,学到什么程度?&/li&&li&对于初学者或大学生的建议有哪些?&/li&&/ul&&p&如果你想听一听我的经验或者有疑惑,欢迎参与。&/p&&p&参与地址:&a href=&/lives/620224& class=&internal&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/lives/8028995&/span&&span class=&invisible&&&/span&&span class=&ellipsis&&&/span&&/a&&/p&
数据库是应用开发中常用的技术,在Android应用中也不例外。Android默认使用了SQLite数据库,在应用程序开发中,我们使用最多的无外乎增删改查。纵使操作简单,也有可能出现查找数据缓慢,插入数据耗时等情况,如果出现了这种问题,我们就需要考虑对数据库操…
&img src=&/50/v2-2ac3f71d1a196e10344b8_b.png& data-rawwidth=&512& data-rawheight=&719& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/50/v2-2ac3f71d1a196e10344b8_r.png&&&blockquote&英文原文:&a href=&/?target=https%3A///staltz/868e7e9bc2a7b8c1f754& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The introduction to Reactive Programming you've been missing&i class=&icon-external&&&/i&&/a&&br&中文链接:&a href=&/?target=http%3A///project/android-weekly/issue-145/introduction-to-RP.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&响应式编程(Reactive Programming)介绍&i class=&icon-external&&&/i&&/a&&br&翻译:极客学院wiki,已获得转载权限&/blockquote&&p&这篇文章在GitHubGist上面获得了12531个star,文章很长,但是希望你耐心读完,感谢极客学院的翻译!&/p&&p&学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下。刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它们都流于表面,从没有围绕响应式编程构建起一个完整的知识体系。库的文档往往也无法帮助你去了解它的函数,不信的话可以看一下这个:&/p&&p&通过合并元素的指针,将每一个可观察的元素序列放射到一个新的可观察的序列中,然后将多个可观察的序列中的一个转换成一个只从最近的可观察序列中产生值得可观察的序列。&/p&&p&天啊。&/p&&p&我看过两本书,一本只是讲述了一些概念,而另一本则纠结于如何使用响应式编程库。我最终放弃了这种痛苦的学习方式,决定在开发中一边使用响应式编程,一边理解它。在 &a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Futurice&i class=&icon-external&&&/i&&/a& 工作期间,我尝试在真实项目中使用响应式编程,并且当我遇到困难时,得到了同事们的帮助。&/p&&p&在学习过程中最困难的一部分是 以响应式编程的方式思考 。这意味着要放弃命令式且带状态的编程习惯,并且要强迫你的大脑以一种不同的方式去工作。在互联网上我找不到任何关于这方面的教程,而我觉得这世界需要一份关于怎么以响应式编程的方式思考的实用教程,这样你就有足够的资料去起步。库的文档无法为你的学习提供指引,而我希望这篇文章可以。&/p&&h2&什么是响应式编程?&/h2&&p&在互联网上有着一大堆糟糕的解释与定义。&a href=&/?target=https%3A//en.wikipedia.org/wiki/Reactive_programming& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Wikipedia&i class=&icon-external&&&/i&&/a& 一如既往的空泛与理论化。&a href=&/?target=http%3A///questions/1028250/what-is-functional-reactive-programming& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Stackoverflow&i class=&icon-external&&&/i&&/a& 的权威答案明显不适合初学者。&a href=&/?target=http%3A//www.reactivemanifesto.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reactive Manifesto&i class=&icon-external&&&/i&&/a& 看起来是你展示给你公司的项目经理或者老板们看的东西。微软的 &a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rx terminology&i class=&icon-external&&&/i&&/a&&Rx = Observables + LINQ + Schedulers& 过于重量级且微软味十足,只会让大部分人困惑。相对于你所使用的 MV* 框架以及钟爱的编程语言,&Reactive& 和 &Propagation of change& 这些术语并没有传达任何有意义的概念。框架的 Views 层当然要对 Models 层作出反应,改变当然会传播。如果没有这些,就没有东西会被渲染了。&/p&&p&所以不要再扯这些废话了。&/p&&h3&响应式编程是使用异步数据流进行编程&/h3&&p&一方面,这并不是什么新东西。Event buses 或者 Click events 本质上就是异步事件流,你可以监听并处理这些事件。响应式编程的思路大概如下:你可以用包括 Click 和 Hover 事件在内的任何东西创建 Data stream。Stream 廉价且常见,任何东西都可以是一个 Stream:变量、用户输入、属性、Cache、数据结构等等。举个例子,想像一下你的 Twitter feed 就像是 Click events 那样的 Data stream,你可以监听它并相应的作出响应。&/p&&p&在这个基础上,你还有令人惊艳的函数去组合、创建、过滤这些 Streams,这就是函数式魔法的用武之地。Stream 能接受一个,甚至多个 Stream 为输入,你可以融合两个 Stream,也可以从一个 Stream 中过滤出你感兴趣的 Events 以生成一个新的 Stream,还可以把一个 Stream 中的数据值 映射到一个新的 Stream 中。&/p&&p&既然 Stream 在响应式编程中如此重要,那么我们就应该好好的了解它们,就从我们熟悉的&Clicks on a button& Event stream 开始。&/p&&img src=&/v2-a54c1dfb377ea7e9ecb55c7_b.png& data-rawwidth=&512& data-rawheight=&261& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/v2-a54c1dfb377ea7e9ecb55c7_r.png&&&p&Stream 就是一个按时间排序的 Events 序列,它可以放射三种不同的 Events:(某种类型的)Value、Error 或者一个& Completed& Signal。考虑一下&Completed&发生的时机,例如,当包含这个按钮的窗口或者视图被关闭时。&/p&&p&通过分别为 Value、Error、&Completed&定义事件处理函数,我们将会异步地捕获这些 Events。有时可以忽略 Error 与&Completed&,你只需要定义 Value 的事件处理函数就行。监听一个 Stream 也被称作是订阅 ,而我们所定义的函数就是观察者,Stream则是被观察者,其实就是 &a href=&/?target=https%3A//en.wikipedia.org/wiki/Observer_pattern& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Observer Design Pattern&i class=&icon-external&&&/i&&/a&。&/p&&p&上面的示意图也可以使用ASCII重画为下图,在下面的部分教程中我们会使用这幅图:&br&&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
--a---b-c---d---X---|-&
a, b, c, d are emitted values
X is an error
| is the 'completed' signal
---& is the timeline
&/code&&/pre&&/div&&p&既然已经开始对响应式编程感到熟悉,为了不让你觉得无聊,我们可以尝试做一些新东西:我们将会把一个 Click event stream 转为新的 Click event stream。&/p&&p&首先,让我们做一个能记录一个按钮点击了多少次的计数器 Stream。在常见的响应式编程库中,每个Stream都会有多个方法,如 map, filter, scan, 等等。当你调用其中一个方法时,例如 clickStream.map(f),它就会基于原来的 Click stream 返回一个新的 Stream 。它不会对原来的 Click steam 作任何修改。这个特性称为不可变性,它对于响应式编程 Stream,就如果汁对于薄煎饼。我们也可以对方法进行链式调用,如 clickStream.map(f).scan(g):&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
clickStream: ---c----c--c----c------c--&
vvvvv map(c becomes 1) vvvv
---1----1--1----1------1--&
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5--&
&/code&&/pre&&/div&&p&map(f) 会根据你提供的 f 函数把原 Stream 中的 Value 分别映射到新的 Stream 中。在我们的例子中,我们把每一次 Click 都映射为数字 1。scan(g) 会根据你提供的 g 函数把 Stream 中的所有 Value 聚合成一个 Value x = g(accumulated, current) ,这个示例中 g 只是一个简单的添加函数。然后,每 Click 一次, counterStream 就会把点击的总次数发给它的观察者。&/p&&p&为了展示响应式编程真正的实力,让我们假设你想得到一个包含“双击”事件的 Stream。为了让它更加有趣,假设我们想要的这个 Stream 要同时考虑三击(Triple clicks),或者更加宽泛,连击(两次或更多)。深呼吸一下,然后想像一下在传统的命令式且带状态的方式中你会怎么实现。我敢打赌代码会像一堆乱麻,并且会使用一些变量保存状态,同时也有一些计算时间间隔的代码。&/p&&p&而在响应式编程中,这个功能的实现就非常简单。事实上,这逻辑只有 &a href=&/?target=http%3A//jsfiddle.net/staltz/4gGgs/27/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&4 行代码&i class=&icon-external&&&/i&&/a&。但现在我们先不管那些代码。用图表的方式思考是理解怎样构建Stream的最好方法,无论你是初学者还是专家。&/p&&img src=&/v2-1642eb54bcb1c4de4e1fdc1eeb794789_b.png& data-rawwidth=&512& data-rawheight=&719& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/v2-1642eb54bcb1c4de4e1fdc1eeb794789_r.png&&&p&灰色的方框是用来转换 Stream 函数的。首先,简而言之,我们把连续 250 ms 内的 Click 都积累到一个列表中(就是 buffer(stream.throttle(250ms) 做的事。不要在意这些细节,我们只是展示一下响应式编程而已)。结果是一个列表的 Stream ,然后我们使用 map() 把每个列表映射为一个整数,即它的长度。最终,我们使用 filter(x &= 2) 把整数 1 给过滤掉。就这样,3 个操作就生成了我们想要的 Stream。然后我们就可以订阅(“监听”)这个 Stream,并以我们所希望的方式作出反应。&/p&&p&我希望你能感受到这个示例的优美之处。这个示例只是冰山一角:你可以把同样的操作应用到不同种类的 Stream 上,例如,一个 API 响应的 Stream;另一方面,还有很多其它可用的函数。&/p&&h2&为什么我要使用响应式编程(RP)?&/h2&&p&响应式编程提高了代码的抽象层级,所以你可以只关注定义了业务逻辑的那些相互依赖的事件,而非纠缠于大量的实现细节。RP 的代码往往会更加简明。&/p&&p&特别是在开发现在这些有着大量与数据事件相关的 UI events 的高互动性 Webapps、手机 apps 的时候,RP 的优势就更加明显。10年前,网页的交互就只是提交一个很长的表单到后端,而在前端只产生简单的渲染。Apps 就表现得更加的实时了:修改一个表单域就能自动地把修改后的值保存到后端,为一些内容&点赞&时,会实时的反应到其它在线用户那里等等。&/p&&p&现在的 Apps 有着大量各种各样的实时 Events,以给用户提供一个交互性较高的体验。我们需要工具去应对这个变化,而响应式编程就是一个答案。&/p&&h2&以 RP 方式思考的例子&/h2&&p&让我们做一些实践。一个真实的例子一步一步的指导我们以 RP 的方式思考。不是虚构的例子,也没有只解释了一半的概念。学完教程之后,我们将写出真实可用的代码,并做到知其然,知其所以然。&/p&&p&在这个教程中,我将会使用 JavaScript 和 &a href=&/?target=https%3A///Reactive-Extensions/RxJS& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&RxJS&i class=&icon-external&&&/i&&/a& 作为工具 ,因为JavaScript是现在最多人会的语言,而 &a href=&/?target=http%3A//www.reactivex.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rx* library family&i class=&icon-external&&&/i&&/a& 有多种语言版本,并支持多种平台(&a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&.NET&i class=&icon-external&&&/i&&/a&, &a href=&/?target=https%3A///Netflix/RxJava& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java&i class=&icon-external&&&/i&&/a&, Scala, Clojure, &a href=&/?target=https%3A///Reactive-Extensions/RxJS& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JavaScript&i class=&icon-external&&&/i&&/a&, &a href=&/?target=https%3A///Reactive-Extensions/Rx.rb& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Ruby&i class=&icon-external&&&/i&&/a&, &a href=&/?target=https%3A///Reactive-Extensions/RxPy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Python&i class=&icon-external&&&/i&&/a&, &a href=&/?target=https%3A///Reactive-Extensions/RxCpp& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&C++&i class=&icon-external&&&/i&&/a&, &a href=&/?target=https%3A///ReactiveCocoa/ReactiveCocoa& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Objective-C/Cocoa&i class=&icon-external&&&/i&&/a&, Groovy等等)。所以,无论你用的是什么工具,你都能从下面这个教程中受益。&/p&&h2&实现Who to follow推荐界面&/h2&&p&在 Twitter 上,这个表明其他账户的 UI 元素看起来是这样的:&/p&&img src=&/v2-0d490fbb90b14db5a85a9f1e03d36cea_b.png& data-rawwidth=&400& data-rawheight=&380& class=&content_image& width=&400&&&p&我们将会重点模拟它的核心功能,如下:&/p&&ul&&li&启动时从 API 那里加载帐户数据,并显示 3 个推荐&/li&&li&点击&Refresh&时,加载另外 3 个推荐用户到这三行中&/li&&li&点击帐户所在行的'x'按钮时,只清除那一个推荐然后显示一个新的推荐&/li&&li&每行都会显示帐户的头像,以及他们主页的链接&/li&&/ul&&p&我们可以忽略其它的特性和按钮,因为它们是次要的。同时,因为 Twitter 最近关闭了对非授权用户的 API,我们将会为 Github 实现这个推荐界面,而非 Twitter。这是&a href=&/?target=https%3A///v3/users/%23get-all-users& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Github获取用户的API&i class=&icon-external&&&/i&&/a&。&/p&&p&如果你想先看一下最终效果,这里有完成后的代码 &a href=&/?target=http%3A//jsfiddle.net/staltz/8jFJH/48/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Intro to Rx - JSFiddle&i class=&icon-external&&&/i&&/a&&a href=&/?target=http%3A//jsfiddle.net/staltz/8jFJH/48/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Intro to Rx - JSFiddle&i class=&icon-external&&&/i&&/a&。&/p&&h2&请求和响应&/h2&&p&在 Rx 中你该怎么处理这个问题呢? 好吧,首先,(几乎) 所有的东西都可以转为一个Stream 。这就是Rx的咒语。让我们先从最简单的特性开始:&在启动时,从API加载3个帐户的数据&。这并没有什么特别,就只是简单的(1)发出一个请求,(2)收到一个响应,(3)渲染这个响应。所以,让我们继续,并用Stream代表我们的请求。一开始可能会觉得杀鸡用牛刀,但我们应当从最基本的开始,对吧?&/p&&p&在启动的时候,我们只需要发出一个请求,所以如果我们把它转为一个Data stream的话,那就是一个只有一个Value的Stream。稍后,我们知道将会有多个请求发生,但现在,就只有一个请求。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
--a------|-&
Where a is the string '/users'
&/code&&/pre&&/div&&p&这是一个我们想向其发出请求的 URL 的 Stream。每当一个请求事件发生时,它会告诉我们两件事:&什么时候&与&什么东西&。&什么时候&这个请求会被执行,就是什么时候这个 Event 会被映射。&什么东西&会被请求,就是这个映射出来的值:一个包含 URL 的 String。&/p&&p&在 RX&em& 中,创建只有一个值的 Stream 是非常简单的。官方把一个 Stream 称作“Observable”,因为它可以被观察,但是我发现那是个很愚蠢的名子,所以我把它叫做 &/em&Stream*。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'/users'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&但是现在,那只是一个包含了String的Stream,并没有其他操作,所以我们需要以某种方式使那个值被映射。就是通过 &a href=&/?target=https%3A///Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&subscribing&i class=&icon-external&&&/i&&/a& 这个 Stream。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&nx&&requestStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// execute the request&/span&
&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&responseData&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// ...&/span&
&span class=&p&&});&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&留意一下我们使用了 jQuery 的 Ajax 函数(我们假设你已经知道 &a href=&/?target=http%3A//devdocs.io/jquery/jquery.getjson& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&should know already&i class=&icon-external&&&/i&&/a&)去处理异步请求操作。但先等等,Rx 可以用来处理异步 Data stream。那这个请求的响应就不能当作一个包含了将会到达的数据的 Stream 吗?当然,从理论上来讲,应该是可以的,所以我们尝试一下。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&nx&&requestStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// execute the request&/span&
&span class=&kd&&var&/span& &span class=&nx&&responseStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&create&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span& &span class=&p&&(&/span&&span class=&nx&&observer&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span&
&span class=&p&&.&/span&&span class=&nx&&done&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&nx&&observer&/span&&span class=&p&&.&/span&&span class=&nx&&onNext&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&);&/span& &span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&fail&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&jqXHR&/span&&span class=&p&&,&/span& &span class=&nx&&status&/span&&span class=&p&&,&/span& &span class=&nx&&error&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&nx&&observer&/span&&span class=&p&&.&/span&&span class=&nx&&onError&/span&&span class=&p&&(&/span&&span class=&nx&&error&/span&&span class=&p&&);&/span& &span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&always&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span& &span class=&nx&&observer&/span&&span class=&p&&.&/span&&span class=&nx&&onCompleted&/span&&span class=&p&&();&/span& &span class=&p&&});&/span&
&span class=&p&&});&/span&
&span class=&nx&&responseStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// do something with the response&/span&
&span class=&p&&});&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&&p&&a href=&/?target=https%3A///Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rx.Observable.create()&i class=&icon-external&&&/i&&/a&所做的事就是通过显式的通知每一个 Observer (或者说是“Subscriber”) Data events( onNext() )或者 Errors ( onError() )来创建你自己的 Stream。而我们所做的就只是把 jQuery Ajax Promise 包装起来而已。打扰一下,这意味者Promise本质上就是一个Observable?&/p&&p&是的。&/p&&p&Observable 就是 Promise++。在 Rx 中,你可以用 var stream = Rx.Observable.fromPromise(promise) 轻易的把一个 Promise 转为 Observable,所以我们就这样子做吧。唯一的不同就是 Observable 并不遵循 &a href=&/?target=http%3A//promises-aplus.github.io/promises-spec/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Promises/A+&i class=&icon-external&&&/i&&/a&,但概念上没有冲突。Promise 就是只有一个映射值的 Observable。Rx Stream 比 Promise 更进一步的是允许返回多个值。&/p&&p&这样非常不错,并展现了 Observables 至少有 Promise 那么强大。所以如果你相信 Promise 宣传的那些东西,那么也请留意一下 Rx Observables 能胜任些什么。&/p&&p&现在回到我们的例子,如果你已经注意到了我们在 subscribe() 内又调用了另外一个 subscribe() ,这类似于 Callback hell。同样,你应该也注意到 responseStream 是建立在 requestStream 之上的。就像你之前了解到的那样,在 Rx 内有简单的机制可以从其它 Stream 中转换并创建出新的 Stream,所以我们也应该这样子做。&/p&&p&你现在需要知道的一个基本的函数是 [map(f)](&a href=&/?target=https%3A///Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md%23rxobservableprototypemapselector-thisarg& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reactive-Extensions/RxJS&i class=&icon-external&&&/i&&/a&) ,它分别把 f() 应用到 Stream A 中的每一个值中,并把返回的值放进 Stream B 里。如果我们也对请求 Stream 与响应 Stream 进行同样的处理,我们可以把 Request URL 映射为响应 Promise(而 Promise 可以转为 Streams)。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&responseMetastream&/span& &span class=&o&&=&/span& &span class=&nx&&requestStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromPromise&/span&&span class=&p&&(&/span&&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&然后,我们将会创造一个叫做& Metastream &的怪物:包含 Stream 的 Stream。暂时不需要害怕。Metastream 就是一个 Stream,其中映射的值还是另外一个 Stream。你可以把它想像为 &a href=&/?target=https%3A//en.wikipedia.org/wiki/Pointer_%28computer_programming%29& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&pointers&i class=&icon-external&&&/i&&/a&:每个映射的值都是一个指向其它 Stream 的指针。在我们的例子里,每个请求 URL 都会被映射一个指向包含响应 Promise stream 的指针。&/p&&img src=&/v2-00d076b1a8b5c99e25dd6f70691cc7ad_b.png& data-rawwidth=&512& data-rawheight=&342& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&/v2-00d076b1a8b5c99e25dd6f70691cc7ad_r.png&&&p&Response 的 Metastream 看起来会让人困惑,并且看起来也没有帮到我们什么。我们只想要一个简单的响应 stream,其中每个映射的值应该是 JSON 对象,而不是一个 JSON 对象的'Promise'。是时候介绍 (Mr. Flatmap)(&a href=&/?target=https%3A///Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md%23rxobservableprototypeflatmapselector-resultselector& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Reactive-Extensions/RxJS&i class=&icon-external&&&/i&&/a&) 了:它是 map() 的一个版本,通过把应用到&trunk& Stream 上的所有操作都应用到&branch& Stream 上,可以&flatten& Metastream。Flatmap 并不是用来&修复& Metastream 的,因为 Metastream 也不是一个漏洞,这只是一些用来处理 Rx 中的异步响应的工具。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&responseStream&/span& &span class=&o&&=&/span& &span class=&nx&&requestStream&/span&
&span class=&p&&.&/span&&span class=&nx&&flatMap&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromPromise&/span&&span class=&p&&(&/span&&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&很好。因为响应stream是根据请求 stream定义的,所以 如果 我们后面在请求 stream上发起更多的请求的话,在响应 stream上我们将会得到相应的响应事件,就像预期的那样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&nx&&requestStream&/span&&span class=&o&&:&/span&
&span class=&o&&--&/span&&span class=&nx&&a&/span&&span class=&o&&-----&/span&&span class=&nx&&b&/span&&span class=&o&&--&/span&&span class=&nx&&c&/span&&span class=&o&&------------|-&&/span&
&span class=&nx&&responseStream&/span&&span class=&o&&:&/span& &span class=&o&&-----&/span&&span class=&nx&&A&/span&&span class=&o&&--------&/span&&span class=&nx&&B&/span&&span class=&o&&-----&/span&&span class=&nx&&C&/span&&span class=&o&&---|-&&/span&
&span class=&p&&(&/span&&span class=&nx&&lowercase&/span& &span class=&nx&&is&/span& &span class=&nx&&a&/span& &span class=&nx&&request&/span&&span class=&p&&,&/span& &span class=&nx&&uppercase&/span& &span class=&nx&&is&/span& &span class=&nx&&its&/span& &span class=&nx&&response&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&现在,我们终于有了一个响应 stream,所以可以把收到的数据渲染出来了:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&nx&&responseStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// render `response` to the DOM however you wish&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&把目前为止所有的代码放到一起就是这样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'/users'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&responseStream&/span& &span class=&o&&=&/span& &span class=&nx&&requestStream&/span&
&span class=&p&&.&/span&&span class=&nx&&flatMap&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromPromise&/span&&span class=&p&&(&/span&&span class=&nx&&jQuery&/span&&span class=&p&&.&/span&&span class=&nx&&getJSON&/span&&span class=&p&&(&/span&&span class=&nx&&requestUrl&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&span class=&nx&&responseStream&/span&&span class=&p&&.&/span&&span class=&nx&&subscribe&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&response&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// render `response` to the DOM however you wish&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&h2&刷新按钮&/h2&&p&我之前并没有提到返回的 JSON 是一个有着 100 个用户数据的列表。因为这个 API 只允许我们设置偏移量,而无法设置返回的用户数,所以我们现在是只用了 3 个用户的数据而浪费了另外 97 个的数据。这个问题暂时可以忽略,稍后我们会学习怎么缓存这些数据。&/p&&p&每点击一次刷新按钮,请求 stream 就会映射一个新的 URL,同时我们也能得到一个新的响应。我们需要两样东西:一个是刷新按钮上 Click events 组成的 Stream(咒语:一切都能是 Stream),同时我们需要根据刷新 click stream 而改变请求 stream。幸运的是,RxJS 提供了从 Event listener 生成 Observable 的函数。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&refreshButton&/span& &span class=&o&&=&/span& &span class=&nb&&document&/span&&span class=&p&&.&/span&&span class=&nx&&querySelector&/span&&span class=&p&&(&/span&&span class=&s1&&'.refresh'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&refreshClickStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&fromEvent&/span&&span class=&p&&(&/span&&span class=&nx&&refreshButton&/span&&span class=&p&&,&/span& &span class=&s1&&'click'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&既然刷新 click event 本身并没有提供任何要请求的 API URL,我们需要把每一次的 Click 都映射为一个实际的 URL。现在,我们把刷新 click stream 改为新的请求 stream,其中每一个 Click 都分别映射为带有随机偏移量的 API 端点。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&因为我比较笨并且也没有使用自动化测试,所以我刚把之前做好的一个特性毁掉了。现在在启动时不会再发出任何的请求,而只有在点击刷新按钮时才会。额...这两个行为我都需要:无论是点击刷新按钮时还是刚打开页面时都该发出一个请求。&/p&&p&我们知道怎么分别为这两种情况生成 Stream:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestOnRefreshStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&span class=&kd&&var&/span& &span class=&nx&&startupRequestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'/users'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&但我们怎样才能把这两个&融合&为一个呢?好吧,有 &a href=&/?target=https%3A///Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&merge()&i class=&icon-external&&&/i&&/a& 函数。这就是它做的事的图解:&/p&&div class=&highlight&&&pre&&code class=&language-python&&&span&&/span&
&span class=&n&&stream&/span& &span class=&n&&A&/span&&span class=&p&&:&/span& &span class=&o&&---&/span&&span class=&n&&a&/span&&span class=&o&&--------&/span&&span class=&n&&e&/span&&span class=&o&&-----&/span&&span class=&n&&o&/span&&span class=&o&&-----&&/span&
&span class=&n&&stream&/span& &span class=&n&&B&/span&&span class=&p&&:&/span& &span class=&o&&-----&/span&&span class=&n&&B&/span&&span class=&o&&---&/span&&span class=&n&&C&/span&&span class=&o&&-----&/span&&span class=&n&&D&/span&&span class=&o&&--------&&/span&
&span class=&n&&vvvvvvvvv&/span& &span class=&n&&merge&/span& &span class=&n&&vvvvvvvvv&/span&
&span class=&o&&---&/span&&span class=&n&&a&/span&&span class=&o&&-&/span&&span class=&n&&B&/span&&span class=&o&&---&/span&&span class=&n&&C&/span&&span class=&o&&--&/span&&span class=&n&&e&/span&&span class=&o&&--&/span&&span class=&n&&D&/span&&span class=&o&&--&/span&&span class=&n&&o&/span&&span class=&o&&-----&&/span&
&/code&&/pre&&/div&&p&这样就简单了:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestOnRefreshStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&span class=&kd&&var&/span& &span class=&nx&&startupRequestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'/users'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&merge&/span&&span class=&p&&(&/span&
&span class=&nx&&requestOnRefreshStream&/span&&span class=&p&&,&/span& &span class=&nx&&startupRequestStream&/span&
&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&还有一个更加简洁的可选方案,不需要使用中间变量。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&merge&/span&&span class=&p&&(&/span&&span class=&nx&&Rx&/span&&span class=&p&&.&/span&&span class=&nx&&Observable&/span&&span class=&p&&.&/span&&span class=&nx&&just&/span&&span class=&p&&(&/span&&span class=&s1&&'/users'&/span&&span class=&p&&));&/span&
&/code&&/pre&&/div&&p&甚至可以更简短,更具有可读性:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&
&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&kd&&function&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&randomOffset&/span& &span class=&o&&=&/span& &span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&floor&/span&&span class=&p&&(&/span&&span class=&nb&&Math&/span&&span class=&p&&.&/span&&span class=&nx&&random&/span&&span class=&p&&()&/span&&span class=&o&&*&/span&&span class=&mi&&500&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&s1&&'/users?since='&/span& &span class=&o&&+&/span& &span class=&nx&&randomOffset&/span&&span class=&p&&;&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&startWith&/span&&span class=&p&&(&/span&&span class=&s1&&'/users'&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&&a href=&/?target=https%3A///Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&startWith()&i class=&icon-external&&&/i&&/a& 函数做的事和你预期的完全一样。无论你输入的 Stream 是怎样,startWith(x) 输出的 Stream 一开始都是 x 。但是还不够 &a href=&/?target=https%3A//en.wikipedia.org/wiki/Don%27t_repeat_yourself& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&DRY&i class=&icon-external&&&/i&&/a&,我重复了 API 终端 string。一种修复的方法是去掉 refreshClickStream 最后的 startWith() ,并在一开始的时候&模拟&一次刷新 Click。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&requestStream&/span& &span class=&o&&=&/span& &span class=&nx&&refreshClickStream&/span&&span class=&p&&.&/span&&span class=&nx&&startWith&/span&&span class=&p&&(&/span&&span class=&s1&&'startup click'&/span&&span class=&p&&)&/span&
&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&(&/spa

我要回帖

更多关于 d100支持dff格式吗 的文章

 

随机推荐