Maxwell's Growth Pathhttps://maxwell-nc.github.io/2017-08-11T00:00:00+08:00RxJava 2.x 使用详解(六) 变换操作符2017-08-11T00:00:00+08:002017-08-11T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-08-11:/android/rxjava2-6.html<h2>前序</h2>
<p>  忙了一段时间后赶紧趁着空闲时间填一下坑,文章其实写了一段时间但是没有发布,最近校对下确保没有错误发出来。本期将介绍下RxJava的变换操作符,也是最后一期常用操作符的介绍。</p>
<h2>Map</h2>
<p>  基本的转换操作符,可以把每一个元素转换成新的元素发射,接收一个<code>Function<T,R></code>作为转换逻辑的操作,下面是例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="s">"int"</span> <span class="o">+</span> <span class="n">integer</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子中map操作符返回了<code>Flowable<String></code>,最终输出的结果为:int1、int2、int3。</p>
<h2>flatMap</h2>
<p>  上面的Map操作符是把每一个元素转换成一个新的元素,但是flatMap操作符是把每一个元素转换成新的被观察者,每个被观察者发射的元素将会合并成新的被观察者,这些元素顺序输出,例如下面的:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3 …</span></pre></div><h2>前序</h2>
<p>  忙了一段时间后赶紧趁着空闲时间填一下坑,文章其实写了一段时间但是没有发布,最近校对下确保没有错误发出来。本期将介绍下RxJava的变换操作符,也是最后一期常用操作符的介绍。</p>
<h2>Map</h2>
<p>  基本的转换操作符,可以把每一个元素转换成新的元素发射,接收一个<code>Function<T,R></code>作为转换逻辑的操作,下面是例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="s">"int"</span> <span class="o">+</span> <span class="n">integer</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子中map操作符返回了<code>Flowable<String></code>,最终输出的结果为:int1、int2、int3。</p>
<h2>flatMap</h2>
<p>  上面的Map操作符是把每一个元素转换成一个新的元素,但是flatMap操作符是把每一个元素转换成新的被观察者,每个被观察者发射的元素将会合并成新的被观察者,这些元素顺序输出,例如下面的:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">((</span><span class="n">Function</span><span class="o"><</span><span class="n">Integer</span><span class="o">,</span> <span class="n">Publisher</span><span class="o"><?>>)</span>
<span class="n">integer</span> <span class="o">-></span> <span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span><span class="n">integer</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码中把每一个just发射的元素转换成新的Flowable,而每一个新的Flowable额外添加一个“a”元素,所以上述的例子输出结果是:a、1、a、2、a、3。</p>
<h2>flatMapIterable</h2>
<p>  flatMapIterable与flatMap类似,但是flatMapIterable是把每一个元素转换成Iterable,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMapIterable</span><span class="o">((</span><span class="n">Function</span><span class="o"><</span><span class="n">Integer</span><span class="o">,</span> <span class="n">Iterable</span><span class="o"><?>>)</span>
<span class="n">integer</span> <span class="o">-></span> <span class="n">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="n">integer</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码把每一个元素转换成一个List,每个list以元素“a”开头,所以上述的例子输出结果是:a、1、a、2、a、3。</p>
<h2>concatMap</h2>
<p>  concatMap操作符合flatMap操作符类似,接收的参数和转换都是类似的,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">concatMap</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="n">integer</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述的例子输出结果是:a、1、a、2、a、3。flatMap操作符内部是使用merge合并元素,concatMap操作符则是通过concat合并元素,前者可能会出现元素交错问题,后者严格按照顺序发射。另外concatMap也有类似的concatMapIterable操作符,这里就不一样介绍了。</p>
<h2>switchMap</h2>
<p>  switchMap用法与flatMap类似,但是转换出来的每一个新的数据(被观察者)发射会取代掉前一个被观察者,如下例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">switchMap</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">timer</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">longValue</span> <span class="o">-></span> <span class="n">integer</span><span class="o">)</span>
<span class="o">)</span><span class="c1">//延迟1s发送元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上面的例子最终只会输出3这个元素,其他元素被覆盖替代掉不再发送。</p>
<h2>cast</h2>
<p>  强制转换每一个元素的类型,内部调用map操作符来进行转换:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">cast</span><span class="o">(</span><span class="n">Number</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码把每个元素都转换成Number类型,然后再发射。</p>
<h2>scan</h2>
<p>  扫描每一个元素,第一个元素将忽略,从第二个元素开始(可以获得上一个元素的值)进行变换后返回,例如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">scan</span><span class="o">((</span><span class="n">last</span><span class="o">,</span> <span class="n">item</span><span class="o">)</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"last:"</span> <span class="o">+</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">last</span><span class="o">));</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"item:"</span> <span class="o">+</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">item</span><span class="o">));</span>
<span class="k">return</span> <span class="n">item</span><span class="o">+</span><span class="mi">1</span><span class="o">;</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出结果为1、3、4。</p>
<h2>buffer</h2>
<p>  buffer操作符是把多个元素打包成一个元素一次过发送数据,例如下面例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">buffer</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span><span class="c1">//三个元素打包成一个元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">intList</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">intList</span><span class="o">.</span><span class="na">toString</span><span class="o">()));</span>
</pre></div>
<p>  上述代码把三个元素组合成一个List发送,输出结果为:[1, 2, 3]、[4, 5]。</p>
<h2>toList</h2>
<p>  把所有元素转换成一个List一次过发送出去,如下面例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">toList</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">intList</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">intList</span><span class="o">.</span><span class="na">toString</span><span class="o">()));</span>
</pre></div>
<p>  上述代码输出[1, 2, 3, 4, 5]。除了toList操作符以外,还有toSortedList操作符,而且toSortedList操作符也支持自定义排序方式,这里就不展开了。</p>
<h2>groupBy</h2>
<p>  groupBy操作符通过Function接收每个数据的分组key,然后返回GroupedFlowable,使用者可以再订阅这个被观察者进行数据输出,由于使用lambda表达式可能加大理解难度,这里给出普通写法的例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">groupBy</span><span class="o">(</span><span class="k">new</span> <span class="n">Function</span><span class="o"><</span><span class="n">Integer</span><span class="o">,</span> <span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">apply</span><span class="o">(</span><span class="n">Integer</span> <span class="n">integer</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//这里的返回值是分组的key</span>
<span class="k">return</span> <span class="n">integer</span> <span class="o">></span> <span class="mi">2</span> <span class="o">?</span> <span class="s">"A组"</span> <span class="o">:</span> <span class="s">"B组"</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">Consumer</span><span class="o"><</span><span class="n">GroupedFlowable</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">>>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">GroupedFlowable</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">Integer</span><span class="o">></span> <span class="n">groupedFlowable</span><span class="o">)</span>
<span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//订阅转换成GroupedFlowable</span>
<span class="n">groupedFlowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">Consumer</span><span class="o"><</span><span class="n">Integer</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Integer</span> <span class="n">integer</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">key</span> <span class="o">=</span> <span class="n">groupedFlowable</span><span class="o">.</span><span class="na">getKey</span><span class="o">();</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">key</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  上述代码将输出:“B组:1、B组:2、A组:3、A组:4、A组:5”,上述代码转换成lambda表达式为:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">groupBy</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">integer</span> <span class="o">></span> <span class="mi">2</span> <span class="o">?</span> <span class="s">"A组"</span> <span class="o">:</span> <span class="s">"B组"</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">groupedFlowable</span> <span class="o">-></span>
<span class="n">groupedFlowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span>
<span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">groupedFlowable</span><span class="o">.</span><span class="na">getKey</span><span class="o">()</span>
<span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">))</span>
<span class="o">));</span>
</pre></div>
<h2>toMap</h2>
<p>  熟悉了groupBy后,toMap也是类似的,可以通过自定义key、value转换成对应的map,如下例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">toMap</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="s">"key"</span> <span class="o">+</span> <span class="n">integer</span><span class="o">)</span><span class="c1">//第一个参数Function返回Map的key</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">map</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">map</span><span class="o">.</span><span class="na">toString</span><span class="o">()));</span>
</pre></div>
<p>  上述代码输出:“{key5=5, key2=2, key4=4, key1=1, key3=3}”,另外toMap还可以支持自定义每个item对应的value值(传入第二个Function处理),这里就不一一介绍了。</p>
<h2>尾声</h2>
<p>  RxJava 2.x中基本常用的操作符到此也说得差不多了,虽然还有很多其他的操作符还没有解析,但是理解了这些,大部分场景都够用了,万变不离其宗,以后有不懂的操作符也可以自行学习了,系列文章接下来将介绍下RxJava处理错误的机制和Android上的应用。</p>RxJava 2.x 使用详解(五) 条件操作符2017-06-30T00:00:00+08:002017-06-30T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-30:/android/rxjava2-5.html<h2>前序</h2>
<p>  本期将会介绍条件操作符,用于控制被观察者开始、停止、跳过的各种条件操作符。本文均使用lambda表达式来书写代码,如果不熟悉的朋友可以在IDE中瞧一瞧原来的接受的参数即可。(本文主要是简化了Predicate类型参数)</p>
<h2>all</h2>
<p>  要判断所有元素是否满足某个条件,可以使用all操作符,它接受一个Predicate,其中的test方法用于判断某个元素是否满足条件,最终输出是否所有元素都满足条件,比如下面例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">integer</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子是判断是否所有元素都大于等于0,all操作符会把被观察者转换成Single<Boolean>类型的被观察者,最终输出结果为true。</p>
<h2>ambArray</h2>
<p>  ambArray操作符可以从多个被观察者中选择第一个发射元素的被观察者进行处理,其他被观察者则抛弃,比如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">ambArray</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">timer</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just …</span></pre></div><h2>前序</h2>
<p>  本期将会介绍条件操作符,用于控制被观察者开始、停止、跳过的各种条件操作符。本文均使用lambda表达式来书写代码,如果不熟悉的朋友可以在IDE中瞧一瞧原来的接受的参数即可。(本文主要是简化了Predicate类型参数)</p>
<h2>all</h2>
<p>  要判断所有元素是否满足某个条件,可以使用all操作符,它接受一个Predicate,其中的test方法用于判断某个元素是否满足条件,最终输出是否所有元素都满足条件,比如下面例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">integer</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子是判断是否所有元素都大于等于0,all操作符会把被观察者转换成Single<Boolean>类型的被观察者,最终输出结果为true。</p>
<h2>ambArray</h2>
<p>  ambArray操作符可以从多个被观察者中选择第一个发射元素的被观察者进行处理,其他被观察者则抛弃,比如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">ambArray</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">timer</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">))</span><span class="c1">//仅处理第一个发射元素的被观察者</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码只会处理第二个被观察者,输出结果为3,4,5。</p>
<h2>contains</h2>
<p>  如果要判断被观察者是否包含某一个元素,可以使用contains操作符,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码判断被观察者发射元素中是否包含“3”,contains操作符会把被观察者转换成Single<Boolean>类型的被观察者,输出结果为true。</p>
<h2>any</h2>
<p>  我们观察contains的源码会发现,实际上它调用的是any操作符,any操作符可以判断是否存在某一个元素满足一定的条件,用法和all操作符类似,也是接受一个Predicate,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">any</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">integer</span> <span class="o">==</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子即寻找是否有元素"3",等价于<code>contains(3)</code>,any操作符也是把被观察者转换成Single<Boolean>类型的被观察者,输出结果为true。</p>
<h2>isEmpty</h2>
<p>  要判断一个被观察者是否发射过元素,可以使用isEmpty操作符,其例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">isEmpty</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出false,可以源码发现isEmpty实际上是调用了all操作符。</p>
<h2>defaultIfEmpty</h2>
<p>  如果你需要在被观察者不发送数据的时候需要发送一个默认的元素,可以使用defaultIfEmpty操作符,其例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
<span class="o">.</span><span class="na">defaultIfEmpty</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  如果被观察者不发送任何数据,则会发送默认数据,上述代码中发送的是“1”,故输出结果为“1”。</p>
<h2>switchIfEmpty</h2>
<p>  defaultIfEmpty操作符只能在被观察者不发送数据时发送一个默认的数据,如果需要发送更多数据,则可以使用switchIfEmpty操作符,发送自定义的被观察者作为替代,比如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
<span class="o">.</span><span class="na">switchIfEmpty</span><span class="o">(</span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码将会输出3,4,5。</p>
<h2>sequenceEqual</h2>
<p>  如果要对比两个被观察者发射的元素队列,可以使用sequenceEqual操作符,它只关心<strong>两个发射队列的元素、元素发射顺序、和最终状态</strong>,而不关心他的时间,下面是一个例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">sequenceEqual</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">0</span><span class="n">L</span><span class="o">,</span> <span class="mi">1L</span><span class="o">,</span> <span class="mi">2L</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子中输出是true,sequenceEqual操作符对于元素的类型也是十分敏感的,假设要自定义Integer和Long类型值相等则认为两个元素相等,可以添加额外的参数isEqual,由于sequenceEqual操作符只能对比两个被观察者,所以使用BiPredicate作为判断类型,下面是例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">sequenceEqual</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">),</span>
<span class="o">(</span><span class="n">num1</span><span class="o">,</span> <span class="n">num2</span><span class="o">)</span> <span class="o">-></span> <span class="n">num1</span><span class="o">.</span><span class="na">longValue</span><span class="o">()</span> <span class="o">==</span> <span class="n">num2</span><span class="o">.</span><span class="na">longValue</span><span class="o">())</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码中两个类型均为Number类型,但如果不使用自定义的isEqual参数,则会返回false,这里使用了判断两个数的Long值相等则相等,所以最后输出结果为true。</p>
<h2>takeUntil</h2>
<p>  如果想执行到某个条件就停止事件,可以使用takeUntil操作符,它接受一个Predicate来定义停止条件,其用法如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">takeUntil</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">integer</span><span class="o">==</span><span class="mi">2</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码表明当元素==2时则停止,所以输出结果为1,2。<strong>这里值得注意的是,输出结果是包含该元素的。</strong>除此之外,takeUntil也可以接受另外一个被观察者,当这个被观察者结束之后则停止第一个被观察者,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">interval</span><span class="o">(</span><span class="mi">100</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">MILLISECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">takeUntil</span><span class="o">(</span><span class="n">Flowable</span><span class="o">.</span><span class="na">timer</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码将会在1秒后停止interval生成的被观察者,所以输出结果只有0,1,2,3,4,5,6,7,8。</p>
<h2>takeWhile</h2>
<p>  takeWhile操作符和takeUntil操作符类似,但是takeWhile只接受Predicate,而且<strong>Predicate中的test方法返回true才执行被观察者的事件</strong>,其例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">interval</span><span class="o">(</span><span class="mi">100</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">MILLISECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">takeWhile</span><span class="o">(</span><span class="n">longItem</span> <span class="o">-></span> <span class="n">longItem</span> <span class="o">!=</span> <span class="mi">5L</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出的结果为0,1,2,3,4,注意是不包含5这个元素的。</p>
<h2>skipUntil</h2>
<p>  skipUntil操作符接收一个被观察者,知道该被观察者发送事件之前,第一个被观察者所有发送的元素将被抛弃,例如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">skipUntil</span><span class="o">(</span><span class="n">Flowable</span><span class="o">.</span><span class="na">timer</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码中,表示intervalRange生成的被观察者前3秒的发送的元素都会被抛弃,所以最终输出结果为3,4,5,6,7,8,9。</p>
<h2>skipWhile</h2>
<p>  skipWhile操作符可以接受一个Predicate用于控制<strong>跳过开始一段数据</strong>,比如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">100</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">MILLISECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">skipWhile</span><span class="o">(</span><span class="n">longItem</span> <span class="o">-></span> <span class="n">longItem</span> <span class="o"><</span> <span class="mi">2</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子中,少于2的元素将会被跳过,即输出2,3,4,要注意的是,如果<code>.skipWhile(longItem -> longItem > 2)</code>是不会跳过任何发射元素的,因为skipWhile操作符只会过滤一开始的数据,不能跳过中间或者以后的数据。</p>
<h2>尾声</h2>
<p>  本期学习了各种条件操作符,下期再介绍下变换操作符,对各种元素进行变换操作。</p>RxJava 2.x 使用详解(四) 合并聚合操作符2017-06-29T00:00:00+08:002017-06-29T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-29:/android/rxjava2-4.html<h2>前序</h2>
<p>  这期我们来说一下RxJava中合并操作符和聚合操作符,主要用于合并多个被观察者或者把一个观察者的多个元素聚合成一个元素。文章先从合并操作符开始说明,当切换到聚合操作符时,文章会提到。</p>
<h2>startWith / startWithArray</h2>
<p>  如果你需要在被观察发送元素之前追加数据或者追加新的被观察者,这时候可以使用startWith操作符,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="mi">6</span><span class="o">)</span>
<span class="o">.</span><span class="na">startWith</span><span class="o">(</span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">))</span>
<span class="o">.</span><span class="na">startWith</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出结果为“0,1,2,3,4,5,6”,如果你需要追加多个元素,则需要使用另外一个操作符startWithArray,一次追加多个元素。</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="mi">6</span><span class="o">)</span>
<span class="o">.</span><span class="na">startWithArray</span><span class="o">(</span><span class="mi">1 …</span></pre></div><h2>前序</h2>
<p>  这期我们来说一下RxJava中合并操作符和聚合操作符,主要用于合并多个被观察者或者把一个观察者的多个元素聚合成一个元素。文章先从合并操作符开始说明,当切换到聚合操作符时,文章会提到。</p>
<h2>startWith / startWithArray</h2>
<p>  如果你需要在被观察发送元素之前追加数据或者追加新的被观察者,这时候可以使用startWith操作符,例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="mi">6</span><span class="o">)</span>
<span class="o">.</span><span class="na">startWith</span><span class="o">(</span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">))</span>
<span class="o">.</span><span class="na">startWith</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出结果为“0,1,2,3,4,5,6”,如果你需要追加多个元素,则需要使用另外一个操作符startWithArray,一次追加多个元素。</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="mi">6</span><span class="o">)</span>
<span class="o">.</span><span class="na">startWithArray</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出结果为“1,2,3,4,5,6”,实际上上述两个操作符从源码中观察,都可以发现它实际上使用的concat和concatArray操作符去实现的。下面我们来说明下这两个操作符。</p>
<h2>concat / concatArray</h2>
<p>  concat操作符可以连接最多4个的被观察者,他们的顺序是<strong>串行执行</strong>的:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">concat</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span><span class="mi">5</span><span class="o">,</span><span class="mi">6</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出结果为“1,2,3,4,5,6”,如果需要多于4个被观察合并在一起,可以使用concatArray操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">concatArray</span><span class="o">(</span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">),</span> <span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">2</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<h2>merge / mergeArray</h2>
<p>  上面说到的concat操作符,对于每一个被观察者都是按照顺序串行执行的,接下来介绍的merge操作符也是合并多个被观察者,但他们合并后是<strong>按照时间线并行执行</strong>,举个例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">merge</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子中,两个被观察将并行执行,输出结果为"0,3->1,4->2,5",可以直观的地观察到和concat操作符的区别。</p>
<p>  和concat操作符一样,merge也是最多只能合并4个被观察者,可以使用mergeArray来合并多个被观察者,这里就不举例子了。</p>
<h2>concatDelayError / mergeDelayError</h2>
<p>  使用concat和merge操作符时,如果遇到其中一个被观察者发出onError事件则会马上终止其他被观察者的事件,如果希望onError事件推迟到其他被观察者都结束后才触发,可以使用对应的concatDelayError或者mergeDelayError操作符,比如:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">mergeDelayError</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">s</span> <span class="o">-></span> <span class="n">s</span><span class="o">.</span><span class="na">onError</span><span class="o">(</span><span class="k">new</span> <span class="n">NullPointerException</span><span class="o">()),</span> <span class="n">BackpressureStrategy</span><span class="o">.</span><span class="na">ERROR</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  concatArray和mergeArray也可以使用对应的操作符:<code>concatArrayDelayError</code>和<code>mergeArrayDelayError</code>,这里就不一一列举例子了。</p>
<h2>zip</h2>
<p>  多个被观察者压缩成单个的操作可以使用zip操作符,如果多个被观察者数量不同,则以少的为基准,可以使用Funtions来自定义zip操作(zipper):</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">zip</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">),</span>
<span class="o">(</span><span class="n">int1</span><span class="o">,</span> <span class="n">int2</span><span class="o">)</span> <span class="o">-></span> <span class="n">int1</span> <span class="o">+</span> <span class="n">int2</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码中输出的结果为5、7。</p>
<p>  对于delayError的操作是通过参数传递进去的,在zipper参数之后。zip操作符最多接受9个被观察者,这里的zipper使用到的Functions按照被观察个数分别对应Function、BiFunction和Function3 - Function9,其中的apply方法用于对应的操作。</p>
<h2>combineLatest</h2>
<p>  combineLatest类似zip操作符,但它合并时机和zip不一样,zip是一对一合并压缩,combineLatest则是在同一个时间线上,合并最后的元素,举个例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">combineLatest</span><span class="o">(</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1L</span><span class="o">,</span> <span class="mi">2L</span><span class="o">,</span> <span class="mi">3L</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">),</span>
<span class="o">(</span><span class="n">long1</span><span class="o">,</span> <span class="n">long2</span><span class="o">)</span> <span class="o">-></span> <span class="n">long1</span> <span class="o">+</span> <span class="n">long2</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子只会合并 3+0 、3+1 、3+2 ,即输出3、4、5这几个元素。对于第一个观察者中1、2元素被忽略了。</p>
<h2>combineLatestDelayError</h2>
<p>  combineLatest要处理delayError问题,需要使用combineLatestDelayError操作符,这个操作符把combiner的Funtions用Object[]数组的Funtion来代替,而且是作为第一个参数,上述的combineLatest例子使用combineLatestDelayError可以改写成:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">combineLatestDelayError</span><span class="o">(</span>
<span class="n">objects</span> <span class="o">-></span> <span class="o">(</span><span class="n">Long</span><span class="o">)</span> <span class="n">objects</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">+</span> <span class="o">(</span><span class="n">Long</span><span class="o">)</span> <span class="n">objects</span><span class="o">[</span><span class="mi">1</span><span class="o">],</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1L</span><span class="o">,</span> <span class="mi">2L</span><span class="o">,</span> <span class="mi">3L</span><span class="o">),</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<h2>reduce</h2>
<p>  下面我们来介绍聚合操作符,要把一个被观察者的所有元素都聚合成单一的元素,可以使用reduce操作符,比如把所有元素都加起来,代码如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">reduce</span><span class="o">((</span><span class="n">last</span><span class="o">,</span> <span class="n">item</span><span class="o">)</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">last</span> <span class="o">+</span> <span class="s">","</span> <span class="o">+</span> <span class="n">item</span><span class="o">);</span>
<span class="k">return</span> <span class="n">last</span> <span class="o">+</span> <span class="n">item</span><span class="o">;</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  可以看到一开始,先执行1+2,然后用1+2的结果和3相加,最后输出6,相当于把三个元素聚合在一起。</p>
<h2>count</h2>
<p>  如果要统计一个被观察者发送多少个元素可以使用count方法,其代码如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">count</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  count操作符会把观察者转换成Single<Long>类型的被观察者,上述代码会输出3(即原被观察者发送的元素数量)。</p>
<h2>collect</h2>
<p>  collect和reduce操作相似,不过它是需要自己定义收集的容器和收集逻辑,下面举个例子,利用ArrayList收集发射的元素:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span>
<span class="k">new</span> <span class="n">Callable</span><span class="o"><</span><span class="n">ArrayList</span><span class="o"><</span><span class="n">Integer</span><span class="o">>>()</span> <span class="o">{</span><span class="c1">//创建收集容器</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Integer</span><span class="o">></span> <span class="nf">call</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>();</span>
<span class="o">}</span>
<span class="o">},</span> <span class="k">new</span> <span class="n">BiConsumer</span><span class="o"><</span><span class="n">ArrayList</span><span class="o"><</span><span class="n">Integer</span><span class="o">>,</span> <span class="n">Integer</span><span class="o">>()</span> <span class="o">{</span><span class="c1">//收集操作</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">ArrayList</span><span class="o"><</span><span class="n">Integer</span><span class="o">></span> <span class="n">list</span><span class="o">,</span> <span class="n">Integer</span> <span class="n">integer</span><span class="o">)</span>
<span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span><span class="c1">//前者容器,后者数据</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">integer</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
<span class="c1">//lambda写法</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">ArrayList</span><span class="o">::</span><span class="k">new</span><span class="o">,</span> <span class="n">ArrayList</span><span class="o">::</span><span class="n">add</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述操作最终会输出[1, 2, 3]这个ArrayList元素,相当于收集了所有元素的结果。</p>
<h2>尾声</h2>
<p>  现在回头才发现RxJava的操作符实在不少,后面还将讲述RxJava中的条件操作符、变换操作符等等,这些要到后面再慢慢一一说来了。</p>RxJava 2.x 使用详解(三) 过滤操作符2017-06-28T00:00:00+08:002017-06-28T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-28:/android/rxjava2-3.html<h2>前序</h2>
<p>  上篇说到RxJava创建操作符,这一节来说说过滤操作符,还记得上节中说到interval操作符如果没有其他限制的话就会无限发送onNext事件,永远也不会调用onComplete事件,如果需要限制的话,可以使用一些过滤操作符进行限制。</p>
<h2>filter</h2>
<p>  先说一个基本的过滤操作符filter,可以自己设定任意的规则来过滤数据,比如过滤出大于等于2的元素:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="k">new</span> <span class="n">Predicate</span><span class="o"><</span><span class="n">Integer</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">test</span><span class="o">(</span><span class="n">Integer</span> <span class="n">integer</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//过滤出>=2的数据</span>
<span class="k">return</span> <span class="n">integer</span> <span class="o">>=</span> <span class="mi">2</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  上面的结果只会输出2和3,因为<code>boolean test(T t)</code>方法返回true则表示元素数据有效,否则则为无效抛弃。</p>
<h2>take …</h2><h2>前序</h2>
<p>  上篇说到RxJava创建操作符,这一节来说说过滤操作符,还记得上节中说到interval操作符如果没有其他限制的话就会无限发送onNext事件,永远也不会调用onComplete事件,如果需要限制的话,可以使用一些过滤操作符进行限制。</p>
<h2>filter</h2>
<p>  先说一个基本的过滤操作符filter,可以自己设定任意的规则来过滤数据,比如过滤出大于等于2的元素:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="k">new</span> <span class="n">Predicate</span><span class="o"><</span><span class="n">Integer</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">test</span><span class="o">(</span><span class="n">Integer</span> <span class="n">integer</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//过滤出>=2的数据</span>
<span class="k">return</span> <span class="n">integer</span> <span class="o">>=</span> <span class="mi">2</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  上面的结果只会输出2和3,因为<code>boolean test(T t)</code>方法返回true则表示元素数据有效,否则则为无效抛弃。</p>
<h2>take</h2>
<p>  如果需要使用类似request(long)的方法来限制发射数据的数量,可以使用take操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">interval</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">take</span><span class="o">(</span><span class="mi">5</span><span class="o">)</span><span class="c1">//只发射5个元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  take操作符可以采用时间过滤,例如过滤出5秒之内发送的数据:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">interval</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">take</span><span class="o">(</span><span class="mi">5</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//5秒之内的数据(这里输出0,1,2,3)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<h2>takeLast</h2>
<p>  如果要筛选出最后几个元素的话可以使用takeLast操作符,比如选取最后3个元素:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">takeLast</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  也可以通过时间来筛选,比如筛选出最后三秒的数据:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">takeLast</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//最后三秒发送的数据</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  另外使用takeLast操作符筛选时间,可以增加delayError参数(不传默认为false)<code>takeLast(3, TimeUnit.SECONDS, true)</code>来延迟筛选过程中接收到的error。</p>
<p>  使用takeLast要特别注意,首先元素数量是可数的,由于takeLast使用的是buffer,所以<strong>过滤后的数据会一次性发送</strong>(而不是按照例如intervalRange设定的方式发送),当然这里可以指定takeLast使用的bufferSize。</p>
<h2>firstElement / lastElement</h2>
<p>  如果需要选取第一个元素(允许为空),可以使用firstElement操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">firstElement</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  同理,如果要选取最后一个元素(允许为空),可以使用lastElement操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="o">.</span><span class="na">lastElement</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<h2>first / last</h2>
<p>  如果要设置一个默认值(当被观察者不发射任何元素的时候)可以使用first操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
<span class="o">.</span><span class="na">first</span><span class="o">(</span><span class="mi">2</span><span class="o">)</span><span class="c1">//这里的2是默认元素,非第二个元素 </span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述的代码将会输入“2”这个元素。</p>
<p>  同理如果要设置lastElement为空时发送的元素默认值,可以使用last操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
<span class="o">.</span><span class="na">last</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span><span class="c1">//这里的3是默认发射元素,并非第三个元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述的代码将会输入“3”这个元素。</p>
<h2>firstOrError / lastOrError</h2>
<p>  上述说到的firstElement操作符,如果为空元素的时候不会发生任何异常,如果你需要在空的时候抛出异常,可以使用firstOrError操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
<span class="o">.</span><span class="na">firstOrError</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码将会抛出异常。</p>
<p>  上述说到的lastElement操作符也是遇到空元素也是不会发生任何的异常,如果你需要在空的时候抛出异常,可以使用lastOrError操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">()</span>
<span class="o">.</span><span class="na">lastOrError</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码将会抛出异常。</p>
<h2>elementAt / elementAtOrError</h2>
<p>  如果需要指定发射第几个元素(注意这里的参数为索引值),可以使用elementAt操作符(支持越界)</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span><span class="s">"b"</span><span class="o">,</span><span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">elementAt</span><span class="o">(</span><span class="mi">2</span><span class="o">)</span><span class="c1">//指定索引为2的元素,如果不存在则直接完成</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  如果需要设置越界后发送的默认元素,可以添加额外默认值参数:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span><span class="s">"b"</span><span class="o">,</span><span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">elementAt</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span><span class="s">"d"</span><span class="o">)</span><span class="c1">//指定索引为4的元素,如果不存在则发射“d”</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  如果希望越界后抛出异常,可以使用elementAtOrError操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span><span class="s">"b"</span><span class="o">,</span><span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">elementAtOrError</span><span class="o">(</span><span class="mi">3</span><span class="o">)</span><span class="c1">//指定索引值为3的元素,如果不存在则抛出异常</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<h2>ofType</h2>
<p>  假设你需要筛选特定类型的数据,可以采用ofType操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="s">"b"</span><span class="o">)</span>
<span class="o">.</span><span class="na">ofType</span><span class="o">(</span><span class="n">Integer</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  上述代码只会输出1、3,其他元素被抛弃。</p>
<h2>skip / skipLast</h2>
<p>  如果需要跳过若干个元素,或者跳过一段时间,可以使用skip或者skipLast操作符。下面是跳过若干个元素的例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span><span class="s">"b"</span><span class="o">,</span><span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">skip</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
<span class="o">.</span><span class="na">skipLast</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  假设需要跳过一段时间,可以使用重载方法:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">skip</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">skipLast</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<h2>ignoreElements</h2>
<p>  如果你不关心发送的元素,只关心onComplete和onError事件,可以使用ignoreElements操作符,他会把当前被观察者转换成Completable类型的被观察者:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span><span class="s">"b"</span><span class="o">,</span><span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">ignoreElements</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(()</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"complete"</span><span class="o">));</span>
</pre></div>
<h2>distinct / distinctUntilChanged</h2>
<p>  如果想过滤掉重复的元素,可以使用distinct操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">distinct</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码只会输出a、b、c三个元素。</p>
<p>  如果只需要过滤连续重复的元素,则可以使用distinctUntilChanged操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">)</span>
<span class="o">.</span><span class="na">distinctUntilChanged</span><span class="o">()</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码会输出a、b、c、b、c这几个元素。</p>
<h2>timeout</h2>
<p>  如果需要过滤超时操作,可以使用timeout操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">timeout</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码输出0后超时,抛出异常。</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">timeout</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">,</span> <span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(-</span><span class="mi">1L</span><span class="o">))</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码先输出0,然后超时,使用自定义的Flowable输出-1。</p>
<h2>throttleFirst</h2>
<p>  如果你需要在一段时间内只响应第一次的操作,比如说一段时间内连续点击按钮只执行第一次的点击操作,throttleFirst操作符就可以满足这个需求,使用例子如下:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">throttleFirst</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//每1秒中只处理第一个元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述结果为0、2、4、6、8,其中1、3、5、7、9都被过滤了。</p>
<h2>throttleLast / sample</h2>
<p>  除了throttleFirst,有对应的throttleLast操作符,它的功能和sample操作符相同,都是隔一段时间采集一个元素:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">throttleLast</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//每2秒中采集最后一个元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
<span class="c1">//等价于</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">sample</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//每2秒中采集最后一个元素</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述代码只会输出1、3、5、7,之后会直接触发onComplete事件。</p>
<h2>throttleWithTimeout / debounce</h2>
<p>  假设有一种即时显示搜索结果需求,要求一段时间用户不输入才响应请求搜索结果,这样的需求可以使用throttleWithTimeout操作符或者debounce操作符,举个例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">throttleWithTimeout</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//2秒内有新数据则抛弃旧数据</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
<span class="c1">//等价于</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">debounce</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span><span class="c1">//2秒内有新数据则抛弃旧数据</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">ele</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">ele</span><span class="o">)));</span>
</pre></div>
<p>  上述例子中只会输出9这个元素,因为没当接收到一个元素的时候,会等待2秒,如果有新的元素发送,则抛弃旧的元素,使用新的元素,直到2秒过去或者没有新的数据(比如onComplete)。</p>
<h2>尾声</h2>
<p>  终于把过滤的操作符基本地说了一遍,其实每个操作符可能还有很多重载的形式,大家感兴趣的可以自己逐个试试,由于本人生活和工作上都越来越繁忙,所以以后的更新速度可能会放慢下来了。</p>RxJava 2.x 使用详解(二) 创建操作符2017-06-08T00:00:00+08:002017-06-08T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-08:/android/rxjava2-2.html<h2>前序</h2>
<p>  上篇说到RxJava给我们提供各种操作符来处理日常处理操作,今天我们来详细分析下各种操作符的作用和用法。为此我之前花了点时间增加了博文的目录导航功能,方便大家查阅时候使用。</p>
<p>  由于本文主要讲述操作符作用,本文<strong>使用lambda表达式</strong>来简化代码,如果不熟悉lambda的读者可以参考我<a href="https://maxwell-nc.github.io/android/retrolambda.html">之前的文章</a>,如果涉及前文没有提到的类会不使用lambdb来表示。</p>
<h2>just</h2>
<p>  首先回想一下第一篇中说到的被观察者可以采用Flowable、Observable、Single等等的create方法来实现,如果对于只是发送几个数据来说,这个过程未免十分复杂繁琐,可以使用just操作符来简化:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span><span class="s">"test2"</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">str</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">str</span><span class="o">));</span>
</pre></div>
<p>  上述代码相当于顺序调用<code>onNext("test")</code>和<code>onNext("test2")</code>,然后调用onComplete方法。</p>
<p>  另外还可以使用Observable、Single或者Maybe来调用这个操作符。对于Completable则不能使用(因为没有onNext事件),对于Flowable和Observable最多能接收10个参数,也就是发送10个数据,而Single和Maybe只能接收1个参数(只能发送一次onNext事件)。这些都是理解了本质就能明白为什么会这样,所以下面的操作符非特殊情况就不再一一说明各种被观察者使用区别。</p>
<h2>fromArray</h2>
<p>  之前说到的just操作符,最多只能接收10个参数。我们通过观察just操作符的源码发现 …</p><h2>前序</h2>
<p>  上篇说到RxJava给我们提供各种操作符来处理日常处理操作,今天我们来详细分析下各种操作符的作用和用法。为此我之前花了点时间增加了博文的目录导航功能,方便大家查阅时候使用。</p>
<p>  由于本文主要讲述操作符作用,本文<strong>使用lambda表达式</strong>来简化代码,如果不熟悉lambda的读者可以参考我<a href="https://maxwell-nc.github.io/android/retrolambda.html">之前的文章</a>,如果涉及前文没有提到的类会不使用lambdb来表示。</p>
<h2>just</h2>
<p>  首先回想一下第一篇中说到的被观察者可以采用Flowable、Observable、Single等等的create方法来实现,如果对于只是发送几个数据来说,这个过程未免十分复杂繁琐,可以使用just操作符来简化:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span><span class="s">"test2"</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">str</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">str</span><span class="o">));</span>
</pre></div>
<p>  上述代码相当于顺序调用<code>onNext("test")</code>和<code>onNext("test2")</code>,然后调用onComplete方法。</p>
<p>  另外还可以使用Observable、Single或者Maybe来调用这个操作符。对于Completable则不能使用(因为没有onNext事件),对于Flowable和Observable最多能接收10个参数,也就是发送10个数据,而Single和Maybe只能接收1个参数(只能发送一次onNext事件)。这些都是理解了本质就能明白为什么会这样,所以下面的操作符非特殊情况就不再一一说明各种被观察者使用区别。</p>
<h2>fromArray</h2>
<p>  之前说到的just操作符,最多只能接收10个参数。我们通过观察just操作符的源码发现,其实超过2个参数时,它会帮我们调用fromArray操作符,采用fromArray来接收任意长度的数据数组,下面来举个例子说明:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">fromArray</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">integer</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">integer</span><span class="o">)));</span>
</pre></div>
<p>  fromArray可以直接传入一个数组,例如<code>fromArray(new int[]{1, 2, 3})</code>,但是不要直接传递一个list进去,这样它会直接把list当做一个数据元素发送。</p>
<h2>empty</h2>
<p>  上面说到,fromArray可以接受任意长度的数据数组,假设数组元素数量为0会怎么样呢?我们查看fromArray源代码,可以发现,当数据长度为0时,会调用empty操作符。</p>
<p>  empty操作符不会发送任何数据,而是直接发送onComplete事件,我们写一个例子:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">empty</span><span class="o">().</span><span class="na">subscribe</span><span class="o">(</span>
<span class="n">obj</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"next"</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">()),</span>
<span class="n">e</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"error"</span><span class="o">),</span>
<span class="o">()</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"complete"</span><span class="o">));</span>
</pre></div>
<p>  会发现上面的例子只会打印complete,其他回调并不会触发。</p>
<h2>error</h2>
<p>  除了直接发送onComplete,当然就有直接发送onError,error操作符就是调用时候直接发送onError事件给观察者:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="k">new</span> <span class="n">RuntimeException</span><span class="o">(</span><span class="s">"test"</span><span class="o">)).</span><span class="na">subscribe</span><span class="o">(</span>
<span class="n">obj</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"next"</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">()),</span>
<span class="n">e</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"error"</span><span class="o">),</span>
<span class="o">()</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"complete"</span><span class="o">));</span>
</pre></div>
<p>  上面的例子将只会打印error,其他回调并不会触发。</p>
<h2>never</h2>
<p>  下面来介绍另外一个什么都不会发送的操作符never,也不会触发观察者任何的回调:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">never</span><span class="o">().</span><span class="na">subscribe</span><span class="o">(</span>
<span class="n">obj</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"next"</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">()),</span>
<span class="n">e</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"error"</span><span class="o">),</span>
<span class="o">()</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"complete"</span><span class="o">));</span>
</pre></div>
<p>  上面的例子将不会输出任何东西,这个操作符通常用于“测试”用途。</p>
<h2>fromIterable</h2>
<p>  上文说到的fromArray并不能遍历list等集合,采用fromIterable可以遍历可迭代数据集合:</p>
<div class="highlight"><pre><span></span><span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>();</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"a"</span><span class="o">);</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"b"</span><span class="o">);</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"c"</span><span class="o">);</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">fromIterable</span><span class="o">(</span><span class="n">list</span><span class="o">).</span><span class="na">subscribe</span><span class="o">(</span>
<span class="n">s</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">s</span><span class="o">)</span>
<span class="o">);</span>
</pre></div>
<p>  可以看到输出结果是顺序输出列表中的元素,fromIterable和fromArray除了输入数据不同,其他基本是相似的。</p>
<h2>timer</h2>
<p>  接下来我们说一下时间间隔操作符timer,可以指定一段时间发送数据(固定值0L):</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">timer</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="c1">//这里的x是long类型</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">x</span><span class="o">)));</span>
</pre></div>
<p>  上面例子为延迟1秒后调用<code>onNext(0)</code>,然后调用<code>onComplete()</code>事件。其中timer操作符还有重载方法可以接受多一个参数Scheduler,这个后面会介绍到。</p>
<h2>interval</h2>
<p>  上面的timer操作符只能发送一次数据,对于要不断地发送数据,可以采用interval操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">interval</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="c1">//这里的x是0,1,2,3...</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">x</span><span class="o">)));</span>
</pre></div>
<p>  这里interval接受的第一个参数是第一次的延迟,如果忽略(重载方法)则和设定的间隔一样,也可以单独设置Scheduler。</p>
<p>  如果你运行了上面的代码,会发现interval操作符会无限执行,永不停止,那么应该怎么停止呢?这里就需要过滤操作符take了,为避免打乱文章顺序,这个也是后面再详细介绍。</p>
<h2>intervalRange</h2>
<p>  上面说到interval操作符是从0开始发送数据,如果我们需要指定发送范围,那么可以使用intervalRange操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o">.</span><span class="na">intervalRange</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">)</span>
<span class="c1">//x从1-10,初始间隔2秒,之后间隔1秒发送一次</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">x</span><span class="o">)));</span>
</pre></div>
<p>  如同注释中的说明,当x从1开始发送到10后(注意参数10是发送10个数量的意思,类似于request(10)的操作)调用onComplete方法,值得注意的是从最后一个元素发出到onComplete之间并不会有period长度的延迟。</p>
<h2>range / rangeLong</h2>
<p>  如果你不需要延迟发送数据,但是需要确定一个数据的范围可以采用range或者是rangeLong,后者的数据类型是long,可以使用的范围更加广,其他完全是一样的,其用法如下:</p>
<div class="highlight"><pre><span></span><span class="c1">//int类型范围</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">range</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">5</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">x</span><span class="o">)));</span>
<span class="c1">//long类型范围</span>
<span class="n">Flowable</span><span class="o">.</span><span class="na">rangeLong</span><span class="o">(</span><span class="n">Integer</span><span class="o">.</span><span class="na">MAX_VALUE</span><span class="o">,</span> <span class="mi">5L</span><span class="o">)</span>
<span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">x</span><span class="o">)));</span>
</pre></div>
<p>  上述输出都是从第一个数开始直接输出到最后一个数(第二个参数也是数量,而不是尾数)然后调用onComplete事件,中间没有间隔延迟。</p>
<h2>defer</h2>
<p>  之前说过一个被观察者可以订阅多个观察者,如果需要每个观察者被订阅的时候都重新创建被观察者(一对一),则可以使用defer操作符:</p>
<div class="highlight"><pre><span></span><span class="n">Flowable</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">flowable</span> <span class="o">=</span> <span class="n">Flowable</span><span class="o">.</span><span class="na">defer</span><span class="o">(</span><span class="k">new</span> <span class="n">Callable</span><span class="o"><</span><span class="n">Publisher</span><span class="o"><</span><span class="n">String</span><span class="o">>>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">Publisher</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="nf">call</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Flowable</span><span class="o">.</span><span class="na">just</span><span class="o">(</span><span class="s">"1"</span><span class="o">,</span> <span class="s">"2"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="c1">//订阅第一个观察者</span>
<span class="n">flowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">str</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">str</span><span class="o">));</span>
<span class="c1">//订阅第二个观察者</span>
<span class="n">flowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">str</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">str</span><span class="o">));</span>
</pre></div>
<p>  上面会输出两次(1,2)而且是顺序输出,只有当第一个观察者执行完后才回去创建第二个被观察者然后订阅观察者,然后才开始(第二个被观察者)发送事件消息。</p>
<h2>尾声</h2>
<p>  上文主要介绍了RxJava中的常用创建操作符,本来打算一篇写完全部操作符,但是写着写着发现篇幅已经过长,于是重新整理了一下,对操作符也分下类,方便阅读,下篇继续详解更多的操作符。</p>RxJava 2.x 使用详解(一) 快速入门2017-06-06T00:00:00+08:002017-06-06T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-06:/android/rxjava2-1.html<h2>前序</h2>
<p>  最近打算写一篇完整的RxJava/RxAndroid 2.x使用详解,一方面网上没有找到比较完整的教程,完整的教程又不是面向基础读者,或者还停留在RxJava 1.x的版本中,一方面自己可以当做一个学习笔记,什么时候忘记了也可以快速查一下。</p>
<p>  本文就不再讨论Rxjava 1.x和2.x两个版本有什么区别,关于这个可以参考<a href="https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0">官方wiki</a>,可能现在还看不懂,没关系,可以直接学习2.x,我认为新的迟早要普及,旧版项目也会迁移到新的上。</p>
<p>  另外本文<strong>尽量不使用</strong>Lambda表达式,方便读者理解。</p>
<h2>概念</h2>
<p>  首先知道什么是RxJava,Rx是ReactiveX的缩写,而ReactiveX是Reactive Extensions的缩写。RxJava顾名思义即使Java上的异步和基于事件响应式编程库。</p>
<p>  RxJava基于观察者模式,主要有四个部分:观察者、被观察者、订阅、事件。这样说很难说的明白,我们马上举一个例子来说明。</p>
<h2>Hello RxJava</h2>
<p>  首先根据上面说的,首先需要一个观察者,可以通过创建FlowableSubscriber,由于事件处理的数据类型不一样,FlowableSubscriber需要一个泛型来说明这个数据类型,这里假设我们要处理String类型数据 …</p><h2>前序</h2>
<p>  最近打算写一篇完整的RxJava/RxAndroid 2.x使用详解,一方面网上没有找到比较完整的教程,完整的教程又不是面向基础读者,或者还停留在RxJava 1.x的版本中,一方面自己可以当做一个学习笔记,什么时候忘记了也可以快速查一下。</p>
<p>  本文就不再讨论Rxjava 1.x和2.x两个版本有什么区别,关于这个可以参考<a href="https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0">官方wiki</a>,可能现在还看不懂,没关系,可以直接学习2.x,我认为新的迟早要普及,旧版项目也会迁移到新的上。</p>
<p>  另外本文<strong>尽量不使用</strong>Lambda表达式,方便读者理解。</p>
<h2>概念</h2>
<p>  首先知道什么是RxJava,Rx是ReactiveX的缩写,而ReactiveX是Reactive Extensions的缩写。RxJava顾名思义即使Java上的异步和基于事件响应式编程库。</p>
<p>  RxJava基于观察者模式,主要有四个部分:观察者、被观察者、订阅、事件。这样说很难说的明白,我们马上举一个例子来说明。</p>
<h2>Hello RxJava</h2>
<p>  首先根据上面说的,首先需要一个观察者,可以通过创建FlowableSubscriber,由于事件处理的数据类型不一样,FlowableSubscriber需要一个泛型来说明这个数据类型,这里假设我们要处理String类型数据,代码如下:</p>
<div class="highlight"><pre><span></span><span class="c1">//创建观察者</span>
<span class="n">FlowableSubscriber</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">subscriber</span> <span class="o">=</span> <span class="k">new</span> <span class="n">FlowableSubscriber</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="o">(</span><span class="n">Subscription</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//订阅时候的操作</span>
<span class="n">s</span><span class="o">.</span><span class="na">request</span><span class="o">(</span><span class="n">Long</span><span class="o">.</span><span class="na">MAX_VALUE</span><span class="o">);</span><span class="c1">//请求多少事件,这里表示不限制</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onNext</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//onNext事件处理</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onError</span><span class="o">(</span><span class="n">Throwable</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//onError事件处理</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onComplete</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">//onComplete事件处理</span>
<span class="o">}</span>
<span class="o">};</span>
</pre></div>
<p>  可以看到观察者身上有onNext、onError、onComplete这几个事件,接下来我们需要一个被观察者。RxJava中需要使用<code>create()</code>方法去创建:</p>
<div class="highlight"><pre><span></span><span class="c1">//被观察者</span>
<span class="n">Flowable</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">flowable</span> <span class="o">=</span> <span class="n">Flowable</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="k">new</span> <span class="n">FlowableOnSubscribe</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">subscribe</span><span class="o">(</span><span class="n">FlowableEmitter</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">e</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//订阅观察者时的操作</span>
<span class="n">e</span><span class="o">.</span><span class="na">onNext</span><span class="o">(</span><span class="s">"test1"</span><span class="o">);</span>
<span class="n">e</span><span class="o">.</span><span class="na">onNext</span><span class="o">(</span><span class="s">"test2"</span><span class="o">);</span>
<span class="n">e</span><span class="o">.</span><span class="na">onComplete</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">},</span> <span class="n">BackpressureStrategy</span><span class="o">.</span><span class="na">BUFFER</span><span class="o">);</span>
</pre></div>
<p>  这里先忽略BackpressureStrategy是什么东西,后面会说到,被观察者执行各种操作,最后需要通过<code>subscribe()</code>订阅观察者:</p>
<div class="highlight"><pre><span></span><span class="n">flowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">subscriber</span><span class="o">);</span>
</pre></div>
<p>  值得注意的是<strong>一个被观察者可以订阅多个观察者</strong>。然后尝试运行上述代码,发现LogCat顺序输出"test1"和"test2",可以知道onNext是顺序执行的,一个正常的事件队列情况应该如下:</p>
<ul>
<li>onNext -> onNext ... -> onComplete</li>
<li>onNext -> onNext ... -> onError</li>
</ul>
<h2>Actions</h2>
<p>  上面可以看到FlowableSubscriber中我们只关心onNext方法,其他方法如果我们我们不需要,那么可以用Consumer(Java 8读者注意导包)来作为观察者:</p>
<div class="highlight"><pre><span></span><span class="n">flowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">Consumer</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//相当于onNext事件处理</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"Consumer"</span><span class="o">,</span> <span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  当我们查看<code>subscribe()</code>的重载的时候会发现,如果你关心有多个重载方法,其中有一组用于简化观察者的:</p>
<div class="highlight"><pre><span></span><span class="n">subscribe</span><span class="o">(</span><span class="n">onNext</span><span class="o">)</span> <span class="c1">//即上面的例子</span>
<span class="n">subscribe</span><span class="o">(</span><span class="n">onNext</span><span class="o">,</span><span class="n">onError</span><span class="o">)</span>
<span class="n">subscribe</span><span class="o">(</span><span class="n">onNext</span><span class="o">,</span><span class="n">onError</span><span class="o">,</span><span class="n">onComplete</span><span class="o">)</span>
<span class="n">subscribe</span><span class="o">(</span><span class="n">onNext</span><span class="o">,</span><span class="n">onError</span><span class="o">,</span><span class="n">onComplete</span><span class="o">,</span><span class="n">onSubscribe</span><span class="o">)</span>
</pre></div>
<p>  这里给出一个完整参数的例子,其他重载方法参考即可:</p>
<div class="highlight"><pre><span></span><span class="n">flowable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span>
<span class="k">new</span> <span class="n">Consumer</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span><span class="c1">//相当于onNext</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">},</span> <span class="k">new</span> <span class="n">Consumer</span><span class="o"><</span><span class="n">Throwable</span><span class="o">>()</span> <span class="o">{</span><span class="c1">//相当于onError</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Throwable</span> <span class="n">throwable</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">},</span> <span class="k">new</span> <span class="n">Action</span><span class="o">()</span> <span class="o">{</span><span class="c1">//相当于onComplete,注意这里是Action</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">},</span> <span class="k">new</span> <span class="n">Consumer</span><span class="o"><</span><span class="n">Subscription</span><span class="o">>()</span> <span class="o">{</span><span class="c1">//相当于onSubscribe</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Subscription</span> <span class="n">subscription</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  上面出现了一个Action,那么这个Action和Consumer是什么关系呢?实际上,这两个都是属于<strong>Actions</strong>:</p>
<ul>
<li><strong>Action</strong>:无参数类型</li>
<li><strong>Consumer<T></strong>:单一参数类型</li>
</ul>
<p>  可以看到onComplete方法在FlowableSubscriber中属于无参数方法,而其他属于单一参数方法,自然对应使用Action和Consumer。这时候你可能会产生一个疑问,假如有2个参数呢?3个参数呢?...这里就需要使用:</p>
<ul>
<li><strong>BiConsumer<T1, T2></strong>:双参数类型</li>
<li><strong>Consumer<Obejct[]></strong>:多参数类型</li>
</ul>
<p>  这里暂时没有使用到,后面用到再说明,就不再展开讨论了。</p>
<h2>Observable和Observer</h2>
<p>  被观察者除了Flowable以外还有Observable,它的使用方法和Flowable大体相似,也是使用<code>create()</code>创建:</p>
<div class="highlight"><pre><span></span><span class="c1">//被观察者</span>
<span class="n">Observable</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">observable</span> <span class="o">=</span> <span class="n">Observable</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="k">new</span> <span class="n">ObservableOnSubscribe</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">subscribe</span><span class="o">(</span><span class="n">ObservableEmitter</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">e</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//订阅观察者时的操作</span>
<span class="n">e</span><span class="o">.</span><span class="na">onNext</span><span class="o">(</span><span class="s">"test1"</span><span class="o">);</span>
<span class="n">e</span><span class="o">.</span><span class="na">onNext</span><span class="o">(</span><span class="s">"test2"</span><span class="o">);</span>
<span class="n">e</span><span class="o">.</span><span class="na">onComplete</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">});</span><span class="c1">//没有背压设置</span>
</pre></div>
<p>  由于Observable不支持订阅Subscriber观察者,需要使用Observer作为观察者,其实现方式和Subscriber大体相似:</p>
<div class="highlight"><pre><span></span><span class="c1">//观察者</span>
<span class="n">Observer</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Observer</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="o">(</span><span class="n">Disposable</span> <span class="n">d</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//订阅时候的操作,无需request</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onNext</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//onNext事件处理</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"observer"</span><span class="o">,</span> <span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onError</span><span class="o">(</span><span class="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//onError事件处理</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onComplete</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">//onComplete事件处理</span>
<span class="o">}</span>
<span class="o">};</span>
</pre></div>
<p>  订阅操作也是通过<code>subscribe()</code>来操作:</p>
<div class="highlight"><pre><span></span><span class="n">observable</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="n">observer</span><span class="o">);</span>
</pre></div>
<p>  也可以通过上文提到的Actions来简化观察者,其写法和Flowable的subscribe方法是完全一样的,这里就不再展开讨论了。</p>
<h2>Observable和Flowable</h2>
<p>  下面我们观察下上面两个例子,发现Observable和Flowable其中前者不需要背压(BackPressure)参数和请求资源(request)操作,其他都是大体相似的,那么他们两个有什么区别呢?分别什么时候用呢?</p>
<p>  这两者区别十分明显,Observable不支持背压,而Flowable支持背压(背压是什么?后面再说,先明白区别)。关键是什么时候用呢,下面根据官方的建议:</p>
<p>使用Observable
- 不超过1000个元素、随着时间流逝基本不会出现OOM
- GUI事件或者1000Hz频率以下的元素
- 平台不支持Java Steam(Java8新特性)
- Observable开销比Flowable低</p>
<p>使用Flowable
- 超过10k+的元素(可以知道上限)
- 读取硬盘操作(可以指定读取多少行)
- 通过JDBC读取数据库
- 网络(流)IO操作</p>
<h2>BackPressure(背压)</h2>
<p>  了解了Observable和Flowable的区别,我们还不知什么叫做背压,下面我们来简单了解下概念。所谓背压就是<strong>生产者(被观察者)的生产速度大于消费者(观察者)消费速度</strong>从而导致的问题。</p>
<p>  举一个简单点的例子,如果被观察者快速发送消息,但是观察者处理消息的很缓慢,如果没有特定的流(Flow)控制,就会导致大量消息积压占用系统资源,最终导致十分缓慢。</p>
<p>  怎么优化和减少这种情况后面再探讨,不过可以注意到,Flowable创建的时候已经设置了BackpressureStrategy,而且Subscriber使用了request来控制最大的流量。</p>
<h2>Single和SingleObserver</h2>
<p>  如果你使用一个单一连续事件流,即只有一个onNext事件,接着就触发onComplete或者onError,这样你可以使用Single。</p>
<p>  Single只包含两个事件,一个是正常处理成功的onSuccess,另一个是处理失败的onError,它<strong>只发送一次</strong>消息(当然就不存在背压问题),其中Single类似于Observable,其代码如下:</p>
<div class="highlight"><pre><span></span><span class="c1">//被观察者</span>
<span class="n">Single</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">single</span> <span class="o">=</span> <span class="n">Single</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="k">new</span> <span class="n">SingleOnSubscribe</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">subscribe</span><span class="o">(</span><span class="n">SingleEmitter</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">e</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">onSuccess</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
<span class="n">e</span><span class="o">.</span><span class="na">onSuccess</span><span class="o">(</span><span class="s">"test2"</span><span class="o">);</span><span class="c1">//错误写法,重复调用也不会处理</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="c1">//订阅观察者SingleObserver</span>
<span class="n">single</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">SingleObserver</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="o">(</span><span class="n">Disposable</span> <span class="n">d</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSuccess</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//相当于onNext和onComplete</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onError</span><span class="o">(</span><span class="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  Single也可以使用Actions来简化Observer,还记得BiConsumer这个双参数的Actions吗,正可以用于Single:</p>
<div class="highlight"><pre><span></span><span class="c1">//订阅观察者BiConsumer</span>
<span class="n">single</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">BiConsumer</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">Throwable</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">accept</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">,</span> <span class="n">Throwable</span> <span class="n">throwable</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="c1">//onSuccess和onError操作都在这里处理</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  当然也有单一Actions的模式:</p>
<ul>
<li>single.subscribe(onSuccess)</li>
<li>single.subscribe(onSuccess,onError)</li>
</ul>
<p>  这里就不在展开了。另外Single也可以直接转换成Flowable或者Observable:</p>
<div class="highlight"><pre><span></span><span class="n">single</span><span class="o">.</span><span class="na">toFlowable</span><span class="o">();</span>
<span class="n">single</span><span class="o">.</span><span class="na">toObservable</span><span class="o">();</span>
</pre></div>
<p>  其实他还以转成接下来要说的Completable等,也是使用toXxx的方法。转换之后就可以使用后者独有的方法,这里先不说,后面会有单独篇章详细说明。</p>
<h2>Completable和CompletableObserver</h2>
<p>  如果你的观察者连onNext事件都不关心,你可以使用Completable,他只有onComplete和onError两个事件:</p>
<div class="highlight"><pre><span></span><span class="n">Completable</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="k">new</span> <span class="n">CompletableOnSubscribe</span><span class="o">()</span> <span class="o">{</span><span class="c1">//被观察者</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">subscribe</span><span class="o">(</span><span class="n">CompletableEmitter</span> <span class="n">e</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">onComplete</span><span class="o">();</span><span class="c1">//单一onComplete或者onError</span>
<span class="o">}</span>
<span class="o">}).</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">CompletableObserver</span><span class="o">()</span> <span class="o">{</span><span class="c1">//观察者</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="o">(</span><span class="n">Disposable</span> <span class="n">d</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onComplete</span><span class="o">()</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onError</span><span class="o">(</span><span class="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  同样也可以使用Actions来简化Observer:</p>
<ul>
<li>completable.subscribe(onComplete)</li>
<li>completable.subscribe(onComplete,onError)</li>
</ul>
<p>  要转换成其他类型的被观察者,也是可以使用<code>toFlowable()</code>、<code>toObservable()</code>等方法去转换。</p>
<h2>Maybe和MaybeObserver</h2>
<p>  如果你有一个需求是可能发送一个数据或者不会发送任何数据,这时候你就需要Maybe,它类似于Single和Completable的混合体。</p>
<p>  Maybe可能会调用以下其中一种情况(也就是所谓的Maybe):</p>
<ul>
<li>onSuccess或者onError</li>
<li>onComplete或者onError</li>
</ul>
<p>  可以看到onSuccess和onComplete是互斥的存在,例子代码如下:</p>
<div class="highlight"><pre><span></span><span class="c1">//被观察者</span>
<span class="n">Maybe</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">maybe</span> <span class="o">=</span> <span class="n">Maybe</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="k">new</span> <span class="n">MaybeOnSubscribe</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">subscribe</span><span class="o">(</span><span class="n">MaybeEmitter</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">e</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">e</span><span class="o">.</span><span class="na">onSuccess</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span><span class="c1">//发送一个数据的情况,或者onError,不需要再调用onComplete(调用了也不会触发onComplete回调方法)</span>
<span class="c1">//e.onComplete();//不需要发送数据的情况,或者onError</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="c1">//订阅观察者</span>
<span class="n">maybe</span><span class="o">.</span><span class="na">subscribe</span><span class="o">(</span><span class="k">new</span> <span class="n">MaybeObserver</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="o">(</span><span class="n">Disposable</span> <span class="n">d</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSuccess</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//发送一个数据时,相当于onNext和onComplete,但不会触发另一个方法onComplete</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onComplete</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">//无数据发送时候的onComplete事件</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"onComplete"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onError</span><span class="o">(</span><span class="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  同样可以是用Actions来简化Observer:</p>
<ul>
<li>maybe.subscribe(onSuccess);</li>
<li>maybe.subscribe(onSuccess,onError);</li>
<li>maybe.subscribe(onSuccess,onError,onComplete);</li>
</ul>
<p>  要转换成其他类型的被观察者,也是可以使用<code>toFlowable()</code>、<code>toObservable()</code>等方法去转换。</p>
<h2>尾声</h2>
<p>  通过上述文章,我们初步了解了RxJava的观察者和被观察者,利用Actions来简化观察者,各种观察者的区别和转换。</p>
<p>  但是我们可以看到现阶段的被观察者即使只发送一个onComplete事件也需要一大段的代码,即使使用lambda表达式也显得有点臃肿,下一篇会介绍RxJava的操作符,帮助大家简化日常常用操作,比如通过一个列表创建一个被观察者,这里的话先留下一个悬念了。</p>Android中的Lambda表达式详解2017-06-02T00:00:00+08:002017-06-02T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-02:/android/retrolambda.html<h2>前序</h2>
<p>  Lambda表达式是一个<strong>看上去</strong>很难懂的语法糖,目前有一种趋势表明它越来越火,各种语言都开始支持Lambda表达式。即使你不使用这种语法糖,你也应该去学习了解,否则你可能看不懂很多代码。</p>
<p>  不过由于Java8才引入Lambda表达式,而Android Studio一直都是嵌入JDK(Java Development Kit)1.7,自然要在Android Studio上使用Lamda表达式,这时候只能靠开源力量,自然就有Retrolambda这个Gradle插件。</p>
<p>  当然Lambda的好处、缺点这里就不解析了,当然我认为无论你是否使用,Lambda也是需要学习的一个语法糖。本文为了方便阅读,尽可能避免会涉及到其他Java8的新特性。</p>
<h2>引入Retrolambda</h2>
<p>  首先需要使用Lambda,你要有一个JDK(Java Development Kit)1.8,修改你的项目JDK版本为1.8及以上的版本,如下图所示:
<img alt="jdk" src="../images/retrolambda/1.jpg">
  接下来需要在Application的build.gradle中添加Retrolambda插件(来自mavenCentral依赖库):</p>
<div class="highlight"><pre><span></span>buildscript {
repositories {
mavenCentral()
//...
}
dependencies {
classpath 'me.tatarka:gradle-retrolambda:3 …</pre></div><h2>前序</h2>
<p>  Lambda表达式是一个<strong>看上去</strong>很难懂的语法糖,目前有一种趋势表明它越来越火,各种语言都开始支持Lambda表达式。即使你不使用这种语法糖,你也应该去学习了解,否则你可能看不懂很多代码。</p>
<p>  不过由于Java8才引入Lambda表达式,而Android Studio一直都是嵌入JDK(Java Development Kit)1.7,自然要在Android Studio上使用Lamda表达式,这时候只能靠开源力量,自然就有Retrolambda这个Gradle插件。</p>
<p>  当然Lambda的好处、缺点这里就不解析了,当然我认为无论你是否使用,Lambda也是需要学习的一个语法糖。本文为了方便阅读,尽可能避免会涉及到其他Java8的新特性。</p>
<h2>引入Retrolambda</h2>
<p>  首先需要使用Lambda,你要有一个JDK(Java Development Kit)1.8,修改你的项目JDK版本为1.8及以上的版本,如下图所示:
<img alt="jdk" src="../images/retrolambda/1.jpg">
  接下来需要在Application的build.gradle中添加Retrolambda插件(来自mavenCentral依赖库):</p>
<div class="highlight"><pre><span></span>buildscript {
repositories {
mavenCentral()
//...
}
dependencies {
classpath 'me.tatarka:gradle-retrolambda:3.6.1'
//...
}
}
allprojects {
repositories {
mavenCentral()
//...
}
}
</pre></div>
<p>  然后在需要用用到lambda的模块,比如app模块中的build.gradle文件中设置插件和编译Java版本:</p>
<div class="highlight"><pre><span></span>//...
apply plugin: 'me.tatarka.retrolambda'
android {
//...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
</pre></div>
<p>  如果你的项目使用了混淆,可以添加:</p>
<div class="highlight"><pre><span></span>-dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$*
</pre></div>
<p>  完成了上述操作后执行Gradle Sync然后尝试运行App无误后就代表环境搭建完成。</p>
<p>  注:如果你使用新版的Android Studo和Gradle,你只需要配置JDK和设置compileOptions即可,无需引入插件。</p>
<h2>Hello Lambda(IDE自动转换)</h2>
<p>  由于我们现在不懂Lambda表达,所以我们让Android Studio给我们转换出Lambda表达式。假设我们写了一个点击监听器代码:</p>
<div class="highlight"><pre><span></span><span class="c1">//匿名内部类写法</span>
<span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">btn_click</span><span class="o">).</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="k">new</span> <span class="n">View</span><span class="o">.</span><span class="na">OnClickListener</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="n">View</span> <span class="n">view</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span><span class="s">"hello lambda"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">});</span>
</pre></div>
<p>  会发现插件提示可以转换成Lambda表达式,使用自动完成提示快捷键,选择“Replace with lambda”自动转成Lambda表达式:
<img alt="hint" src="../images/retrolambda/2.jpg">
  最后变成如下的写法:</p>
<div class="highlight"><pre><span></span><span class="c1">//转成lambda表达式写法</span>
<span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">btn_click</span><span class="o">)</span>
<span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="n">view</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span><span class="s">"hello lambda"</span><span class="o">));</span>
</pre></div>
<p>  看上去代码精简了不少,下面我们来学习下Lambda表达式的语法。</p>
<h2>基本形式</h2>
<p>  Lambda表达式无论怎么变化,都有一个基本的形式:</p>
<div class="highlight"><pre><span></span>(参数) -> {表达式}
</pre></div>
<p>  其中符号<code>-></code>是不变的,</p>
<p>  <strong>参数的变化情况</strong>:</p>
<ul>
<li>无参数<ul>
<li><code>() -> exp</code></li>
</ul>
</li>
<li>单一参数<ul>
<li><code>param -> exp</code>(自动推导参数类型,可省略()括号)</li>
</ul>
</li>
<li>非单一参数<ul>
<li><code>(param1,param2) -> exp</code>(自动推导参数类型)</li>
<li><code>(int param1,int param2) -> exp</code>(不能自动推导参数类型)</li>
</ul>
</li>
</ul>
<p>  <strong>表达式的变化情况</strong>:</p>
<ul>
<li>空表达式<ul>
<li><code>param -> {}</code></li>
</ul>
</li>
<li>单行表达式<ul>
<li><code>param -> exp</code>(可忽略{}括号,返回表达式也可以省略return)</li>
</ul>
</li>
<li>非单行表达式<ul>
<li><code>param -> {exp1;exp2;}</code>(表达式可以是return语句)</li>
</ul>
</li>
</ul>
<p>  上述的表达方法可能有些抽象,下面我们来举个例子说明下。
就拿前面我们转换出来的Lambda表达式为例:</p>
<div class="highlight"><pre><span></span><span class="n">view</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span><span class="s">"hello lambda"</span><span class="o">)</span>
</pre></div>
<p>  这里的<code>view</code>属于参数,<code>Log.i("tag","hello lambda")</code>属于表达式,其中参数属于单一参数,表达式属于单行表达式,所以省略了()和{}这两个括号。</p>
<h2>为什么</h2>
<p>  我们探讨一下为什么可以这样写,首先这个方法setOnClickListener接收的匿名内部了已经是固定View.OnClickListener类型,而这个接口也只有onClick一个方法,而注解@Override本身也可以忽略的,也就是说这些都是可以忽略推导出来的,自然就可以写成<code>(参数) -> {表达式}</code>的形式了。也许细心的朋友会问:返回值呢?这我就反问还记得Java的方法签名吗?Java的方法签名中,返回值是无关变量,也就是返回值可以通过方法名和参数列表来确定,这也是为什么重载时不能单一改变方法返回值的原因。</p>
<p>  下面我们看一个带泛型的例子:</p>
<div class="highlight"><pre><span></span><span class="n">List</span><span class="o"><</span><span class="n">Integer</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>();</span>
<span class="c1">//匿名内部类形式</span>
<span class="n">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">list</span><span class="o">,</span> <span class="k">new</span> <span class="n">Comparator</span><span class="o"><</span><span class="n">Integer</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="n">Integer</span> <span class="n">a</span><span class="o">,</span> <span class="n">Integer</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">a</span><span class="o">-</span><span class="n">b</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="c1">//Lambda写法</span>
<span class="n">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">list</span><span class="o">,</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="o">-></span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">);</span>
</pre></div>
<p>  上面的Collections.sort方法泛型<T>是可以根据list的泛型进行推导的,自然lambda表达式就无需泛型声明,而对于单一return表达式<code>{return a-b;}</code>也是可以省略掉return、分号和括号的。关于Comparator接口,大家会发现他不只有一个方法,但是仅仅只有compare方法是必须实现的,也就是当这个匿名内部类只有一个方法的时候,必然是compare方法,当有两个方法的时候也不能使用lambda表达式了。</p>
<h2>Functional Interface(函数式接口)</h2>
<p>  上面说到Comparator接口只有一个抽象方法需要实现,这样的接口在Java 8中称为函数式接口。在Java 8之后的源码中,这样的接口都会使用<code>@FunctionalInterface</code>这个注解去标注。可以查看这个注解的注释,其中写到:</p>
<blockquote>
<p>Conceptually, a functional interface has exactly one abstract method. Since {@linkplain java.lang.reflect.Method#isDefault() default methods} have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does <em>not</em> count toward the interface's abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere.</p>
</blockquote>
<p>  简单说明一下就是指函数式接口只能有一个抽象方法是不包括default标记的方法(有默认实现)和Object中已有的方法。所以回去看看Comparator接口的源码,它既包含<code>boolean equals(Object obj);</code>这种Object中已有的方法,也包含default标记的方法。但是只有compare这样一个抽象方法是必须实现的。</p>
<h2>返回值</h2>
<p>  Lambda表达式除了可以作为参数,也可以作为变量赋值:</p>
<div class="highlight"><pre><span></span><span class="n">OnClickListener</span> <span class="n">listener</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="s">"msg"</span><span class="o">);</span>
<span class="n">btnClick</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="n">listener</span><span class="o">);</span>
</pre></div>
<p>  Lambda表达式的返回值<strong>不一定是</strong>函数式接口。</p>
<h2>变量作用域</h2>
<p>  下面我们来研究lambda表达式中变量作用域,比如说访问外部的类型:</p>
<div class="highlight"><pre><span></span><span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
<span class="n">btnClick</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">a</span><span class="o">)));</span>
</pre></div>
<p>  由于Java8的新特性Effectively final,所以lambda表达式<strong>可以直接访问外部变量而不需要加final,但这个变量不能够修改</strong>,否则IDE报错。</p>
<p>  另外lambda表达式中的<strong>()中的参数名不能与外部变量同名</strong>,否则IDE报错。</p>
<p>  对于lambda表达式的<strong>this是代表对应的外部类</strong>,而不代表匿名内部类本身(这和匿名内部类是不一样的),举个例子说明下:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">AppCompatActivity</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//...</span>
<span class="n">btnClick</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="n">x</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span><span class="c1">//这里的this代表MainActivity</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  使用lambda表达式的时候千万要注意变量的作用域。</p>
<h2>Method References(方法引用)</h2>
<p>  下面我们学习一种lambda表达式简化形式,先举个例子:</p>
<div class="highlight"><pre><span></span><span class="n">List</span><span class="o"><</span><span class="n">Integer</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>();</span>
<span class="c1">//Lambda基本表达式</span>
<span class="n">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">list</span><span class="o">,</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="o">-></span> <span class="n">a</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">b</span><span class="o">));</span>
<span class="c1">//方法引用</span>
<span class="n">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">list</span><span class="o">,</span> <span class="n">Integer</span><span class="o">::</span><span class="n">compareTo</span><span class="o">);</span>
</pre></div>
<p>  方法引用的基本形式就是:</p>
<div class="highlight"><pre><span></span>类名::方法名
</pre></div>
<p>  方法的()没什么意义,所以不需要写,而对于这个形式可能出现的情况包括:</p>
<ul>
<li><code>对象::实例方法</code></li>
<li><code>类::静态方法</code></li>
<li><code>类型对象::实例方法</code></li>
</ul>
<p>  比如说上面的<code>Integer::compareTo</code>就属于<code>类型对象::实例方法</code>这种。再举个例子,比如说<code>System.out::println</code>这种就是属于<code>类::静态方法</code>的形式。</p>
<p>  怎么推导?我们从简化过程来一步步说明。就以上面<code>(a, b) -> a.compareTo(b)</code>这个例子:</p>
<div class="highlight"><pre><span></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="o">-></span> <span class="n">a</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">b</span><span class="o">)</span> <span class="c1">//原始lambda表达式</span>
<span class="c1">//↓↓↓</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="o">-></span> <span class="n">Integer</span><span class="o">.</span><span class="na">compareTo</span><span class="o">(</span><span class="n">b</span><span class="o">)</span> <span class="c1">//compareTo属于Integer类型对象的方法</span>
<span class="c1">//↓↓↓</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="o">-></span> <span class="n">Integer</span><span class="o">.</span><span class="na">compareTo</span> <span class="c1">//a、b有顺序,"(b)"可以省略</span>
<span class="c1">//↓↓↓</span>
<span class="n">Integer</span><span class="o">.</span><span class="na">compareTo</span> <span class="c1">//没有使用参数副号,"(a, b) -> "都可以省略</span>
<span class="c1">//↓↓↓</span>
<span class="nl">Integer:</span><span class="o">:</span><span class="n">compareTo</span> <span class="c1">//为了区分,使用::符号</span>
</pre></div>
<p>  另外,如果lambda表达式可以转换成方法引用,Android Studio也是会提示的。<strong>一般只用到一个已存在的方法并且没有额外的参数就可以使用方法引用来表示lambda表达式。</strong></p>
<h2>Constructor References(构造方法引用)</h2>
<p>  下面介绍构造方法引用,实际上有人也把它归为方法引用,因为他的形式是一样的,只是::符号后面跟的方法名变成固定的new,即:</p>
<div class="highlight"><pre><span></span>类名::new
</pre></div>
<p>  这个举例子通常用到新的API,为了防止陌生面孔出现,我们这里通过自定义例子来说明,首先要定义函数式接口,因为方法引用的参数肯定是函数式接口:</p>
<div class="highlight"><pre><span></span><span class="nd">@FunctionalInterface</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Wrapper</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="o">{</span><span class="c1">//对应Java8中提供的Supplier</span>
<span class="n">T</span> <span class="nf">get</span><span class="o">();</span>
<span class="o">}</span>
</pre></div>
<p>  然后写一个打印字符串是否为空的方法(仅仅为了说明,简单化例子):</p>
<div class="highlight"><pre><span></span><span class="kd">private</span> <span class="o"><</span><span class="n">T</span> <span class="kd">extends</span> <span class="n">String</span><span class="o">></span> <span class="kt">void</span> <span class="nf">printIsEmpty</span><span class="o">(</span><span class="n">Wrapper</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="n">wrapper</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"tag"</span><span class="o">,</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">get</span><span class="o">().</span><span class="na">isEmpty</span><span class="o">()</span> <span class="o">?</span> <span class="s">"yes"</span> <span class="o">:</span> <span class="s">"no"</span><span class="o">);</span>
<span class="o">}</span>
</pre></div>
<p>  然后调用方法写法:</p>
<div class="highlight"><pre><span></span><span class="c1">//匿名内部类</span>
<span class="n">printIsEmpty</span><span class="o">(</span><span class="k">new</span> <span class="n">Wrapper</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">String</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="c1">//lambda表达式原始写法</span>
<span class="n">printIsEmpty</span><span class="o">(()</span> <span class="o">-></span> <span class="k">new</span> <span class="n">String</span><span class="o">());</span>
<span class="c1">//构造方法引用写法</span>
<span class="n">printIsEmpty</span><span class="o">(</span><span class="n">String</span><span class="o">::</span><span class="k">new</span><span class="o">);</span>
</pre></div>
<p>  注意的是,还有一种构造方法引用是数组型的:</p>
<div class="highlight"><pre><span></span>类名[]::new
</pre></div>
<p>  比如说<code>String[]::new</code>,这里就不举详细的例子了。</p>
<h2>总结</h2>
<p>  以上就是Android中使用Lambda表达式的内容,目前由于版本问题,尚未得到普及,不过例如RxJava等一些框架的文章大部分是使用Lambda表达式的形式说明,所以建议还是需要认真学一学,至少能读懂别人写的吧,至于使用不使用那又是另一回事。</p>Android单元测试之AssertJ框架2017-06-01T00:00:00+08:002017-06-01T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-06-01:/android/assertjTest.html<h2>前序</h2>
<p>  <a href="https://maxwell-nc.github.io/android/robolectricTest.html">上一篇Android单元测试文章</a>说到如果使用Junit给我们提供的Assert去对比Intent,那就需要每个成员都对比一次,十分不方便,今天给大家带来一个十分便利的框架AssertJ,由于这个框架本身是给Java使用的,我们可以直接使用针对Android设计的<a href="https://github.com/square/assertj-android">AssertJ-Android框架</a>。这个框架官方说明中写道“... aims to make it even easier to test Android”(致力于让安卓测试更简单),它和Assertj原版的比较可以看看官方的说明,这个不是本文的重点,这里就不多说了。</p>
<h2>Gradle依赖</h2>
<p>  首先当然是引入AssertJ-Android,直接在app的build.gradle中添加:</p>
<div class="highlight"><pre><span></span>testCompile 'com.squareup.assertj:assertj-android:1.1.1'
</pre></div>
<p>  如果遇到冲突问题,比如:</p>
<blockquote>
<p>Conflict with dependency 'com.android.support:support-annotations' in project ':app'. Resolved versions for …</p></blockquote><h2>前序</h2>
<p>  <a href="https://maxwell-nc.github.io/android/robolectricTest.html">上一篇Android单元测试文章</a>说到如果使用Junit给我们提供的Assert去对比Intent,那就需要每个成员都对比一次,十分不方便,今天给大家带来一个十分便利的框架AssertJ,由于这个框架本身是给Java使用的,我们可以直接使用针对Android设计的<a href="https://github.com/square/assertj-android">AssertJ-Android框架</a>。这个框架官方说明中写道“... aims to make it even easier to test Android”(致力于让安卓测试更简单),它和Assertj原版的比较可以看看官方的说明,这个不是本文的重点,这里就不多说了。</p>
<h2>Gradle依赖</h2>
<p>  首先当然是引入AssertJ-Android,直接在app的build.gradle中添加:</p>
<div class="highlight"><pre><span></span>testCompile 'com.squareup.assertj:assertj-android:1.1.1'
</pre></div>
<p>  如果遇到冲突问题,比如:</p>
<blockquote>
<p>Conflict with dependency 'com.android.support:support-annotations' in project ':app'. Resolved versions for app (23.0.1) and test app (22.2.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.</p>
</blockquote>
<p>  可以排除部分模块,如上述的support-annotations:</p>
<div class="highlight"><pre><span></span>testCompile( 'com.squareup.assertj:assertj-android:1.1.1' ,{
exclude group: 'com.android.support', module: 'support-annotations'
})
</pre></div>
<p>  值得注意,Support-v4和Appcompat也是需要单独的依赖:</p>
<div class="highlight"><pre><span></span>testCompile 'com.squareup.assertj:assertj-android-support-v4:1.1.1'
testCompile 'com.squareup.assertj:assertj-android-appcompat-v7:1.1.1'
</pre></div>
<p>  AssertJ-Android还支持很多其他库,这些依赖都可以在官方文档找到,这里不一一说明。</p>
<h2>快速入门</h2>
<p>  由于AssertJ比较简单,这里就直接给一个简单例子说明下:</p>
<div class="highlight"><pre><span></span><span class="n">Assertions</span><span class="o">.</span><span class="na">assertThat</span><span class="o">(</span><span class="n">actual</span><span class="o">).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="n">expected</span><span class="o">);</span>
</pre></div>
<p>  上面的是比较两个对象的断言,<strong>注意assertThat传入的是actual实际对象</strong>,这个容易和Junit的搞混。</p>
<h2>导包问题</h2>
<p>  注意AssertJ-Android包含了AssertJ和扩展的Android API,分别对应:</p>
<div class="highlight"><pre><span></span><span class="n">org</span><span class="o">.</span><span class="na">assertj</span><span class="o">.</span><span class="na">core</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span>
<span class="n">org</span><span class="o">.</span><span class="na">assertj</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">api</span><span class="o">.</span><span class="na">Assertions</span>
</pre></div>
<p>  使用的时候要注意包名,否则可能会找不到方法。</p>
<h2>链式判断</h2>
<p>  前文说到,使用Junit的Assert判断两个Intent比较麻烦,下面我们以Intent为例子说明链式判断:</p>
<div class="highlight"><pre><span></span><span class="n">Assertions</span><span class="o">.</span><span class="na">assertThat</span><span class="o">(</span><span class="n">actual</span><span class="o">)</span>
<span class="o">.</span><span class="na">hasComponent</span><span class="o">(</span><span class="n">expected</span><span class="o">.</span><span class="na">getComponent</span><span class="o">())</span>
<span class="o">.</span><span class="na">hasAction</span><span class="o">(</span><span class="n">expected</span><span class="o">.</span><span class="na">getAction</span><span class="o">())</span>
<span class="o">.</span><span class="na">hasFlags</span><span class="o">(</span><span class="n">expected</span><span class="o">.</span><span class="na">getFlags</span><span class="o">());</span>
</pre></div>
<p>  首先Assertions.assertThat会根据actual的类型返回对应的AbstractAssert子类,比如这里返回的是IntentAssert,而IntentAssert里面每个方法都是返回IntentAssert,这样可以做到链式调用,快速判断两个对象的属性。而且针对不同的对象他给出的方法也不一样,比如说MapAssert它身上有contains等方法专门针对Map对象来判断,十分方便。这里就不一样介绍了,使用IDE的自动提示功能很快你就能找到需要的方法了。</p>
<h2>自定义Assertions</h2>
<p>  实际上AssertJ-Android就是基于AssertJ基础上添加了大量Android相关API的自定义Assertions。下面我们也来为我们自己写的类自定义Assertions。</p>
<p>  首先假设我们有一个UserInfo类需要我们自定义Assert类:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserInfo</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">userName</span><span class="o">;</span>
<span class="c1">//here is getter and setter...</span>
<span class="o">}</span>
</pre></div>
<p>  然后我们定义一个UserInfoAssert类,前面说过,这些Assert类都是继承自AbstractAssert类,注意泛型即可:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserInfoAssert</span> <span class="kd">extends</span> <span class="n">AbstractAssert</span><span class="o"><</span><span class="n">UserInfoAssert</span><span class="o">,</span> <span class="n">UserInfo</span><span class="o">></span> <span class="o">{</span>
<span class="kd">public</span> <span class="nf">UserInfoAssert</span><span class="o">(</span><span class="n">UserInfo</span> <span class="n">actual</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="n">actual</span><span class="o">,</span> <span class="n">UserInfoAssert</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">UserInfoAssert</span> <span class="nf">hasUserName</span><span class="o">()</span> <span class="o">{</span>
<span class="n">isNotNull</span><span class="o">();</span><span class="c1">//防止actual为空</span>
<span class="n">String</span> <span class="n">userName</span> <span class="o">=</span> <span class="n">actual</span><span class="o">.</span><span class="na">getUserName</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">TextUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">userName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">failWithMessage</span><span class="o">(</span><span class="s">"Expected username was not null but was null."</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  最后需要自定义一个Assertions类给我们提供访问UserInfoAssert:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Assertions</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">UserInfoAssert</span> <span class="nf">assertThat</span><span class="o">(</span><span class="n">UserInfo</span> <span class="n">actual</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">UserInfoAssert</span><span class="o">(</span><span class="n">actual</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  完了之后就可以使用类似AssertJ的写法了:</p>
<div class="highlight"><pre><span></span><span class="n">Assertions</span><span class="o">.</span><span class="na">assertThat</span><span class="o">(</span><span class="k">new</span> <span class="n">UserInfo</span><span class="o">()).</span><span class="na">hasUserName</span><span class="o">();</span>
</pre></div>
<p>  上述代码如无意外就会抛出一个错误,需要注意的只是Assertions的导包问题。</p>
<h2>总结</h2>
<p>  经过一系列单元测试框架介绍,Android单元测试的基本内容就这些了,比如说如果快速编写单元测试用例等等这些,如果需要深入了解的话可以去看看一些相关的书籍。</p>Android单元测试之Robolectric框架2017-05-26T00:00:00+08:002017-05-26T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-26:/android/robolectricTest.html<h2>前序</h2>
<p>  <a href="https://maxwell-nc.github.io/android/mockitoTest.html">上一篇Android单元测试文章</a>最后说道要测试Android代码逻辑,光有JUnit和Mockito是不够的,假设你使用了TextView的setText,用Mockito框架的话,默认的TextView的getText方法会返回null,如果是简单的代码,使用Mockito的桩设置还可以接受,如果是要测试到Activity的生命周期等一些复杂逻辑就显得比较复杂了。</p>
<p>  为了解决这个问题,诞生了Instrumentation、Robolectric等等的测试框架,不过Instrumentation实际上还是要运行代码到平台上测试,耗费大量的时间,我们今天要介绍的是运行在JVM上的Robolectric测试框架。</p>
<p>  PS:本来想找一些参考文章,结果发现网上的文章多半是说一半没有另一半,或者有些可能遇到的问题没有指出。最主要的是官方的文档也是少的可怜,给出的Sample也是旧版本的,完全很多地方都不一样了。</p>
<h2>Robolectric基本原理</h2>
<p>  在使用Robolectric之前我们先要明白Robolectric是如何工作的。比如说我们前文说到的TextView,如果我们使用Mockito,他给我们提供的是Mock后的TextView,而Robolectric给我们提供的是ShadowTextView,这个ShadowTextView实现了TextView身上的方法,但他又与Android的运行环境无关,也就是说他可以像使用TextView一样的方法,但不用在平台上运行代码,大大提高测试效率。</p>
<h2>特别注意事项</h2>
<p>  使用Robolectric要注意它的版本以及其支持的SDK版本,如果不支持千万不要用高版本SDK编译,或者升级Gradle插件和编译器版本,否则运行时会出现各种问题。如果版本太高,例如我尝试使用Gradle Wrapper版本4.0配合Gradle插件3.0.0版本时发现是无法成功测试的。</p>
<p>  本文采用最新的Robolectric 3.x(目前是3.4-rc2 …</p><h2>前序</h2>
<p>  <a href="https://maxwell-nc.github.io/android/mockitoTest.html">上一篇Android单元测试文章</a>最后说道要测试Android代码逻辑,光有JUnit和Mockito是不够的,假设你使用了TextView的setText,用Mockito框架的话,默认的TextView的getText方法会返回null,如果是简单的代码,使用Mockito的桩设置还可以接受,如果是要测试到Activity的生命周期等一些复杂逻辑就显得比较复杂了。</p>
<p>  为了解决这个问题,诞生了Instrumentation、Robolectric等等的测试框架,不过Instrumentation实际上还是要运行代码到平台上测试,耗费大量的时间,我们今天要介绍的是运行在JVM上的Robolectric测试框架。</p>
<p>  PS:本来想找一些参考文章,结果发现网上的文章多半是说一半没有另一半,或者有些可能遇到的问题没有指出。最主要的是官方的文档也是少的可怜,给出的Sample也是旧版本的,完全很多地方都不一样了。</p>
<h2>Robolectric基本原理</h2>
<p>  在使用Robolectric之前我们先要明白Robolectric是如何工作的。比如说我们前文说到的TextView,如果我们使用Mockito,他给我们提供的是Mock后的TextView,而Robolectric给我们提供的是ShadowTextView,这个ShadowTextView实现了TextView身上的方法,但他又与Android的运行环境无关,也就是说他可以像使用TextView一样的方法,但不用在平台上运行代码,大大提高测试效率。</p>
<h2>特别注意事项</h2>
<p>  使用Robolectric要注意它的版本以及其支持的SDK版本,如果不支持千万不要用高版本SDK编译,或者升级Gradle插件和编译器版本,否则运行时会出现各种问题。如果版本太高,例如我尝试使用Gradle Wrapper版本4.0配合Gradle插件3.0.0版本时发现是无法成功测试的。</p>
<p>  本文采用最新的Robolectric 3.x(目前是3.4-rc2),测试的SDK API版本为25,Gradle Wrapper为3.3,Gradle插件为2.3.0。另外我用的Android Studio版本也是3.0版本,如果低于2.0的版本的建议升级,否则可能会遇到其他问题(本文就不在讨论了)。</p>
<h2>集成Robolectric</h2>
<p>  首先第一步是添加Gradle编译依赖,由于Robolectric本身比较大,所以对于一些功能,它采用add-on的方式,除了核心包其他都是可选添加的,编辑app下的build.gradle文件:</p>
<div class="highlight"><pre><span></span>dependencies {
testCompile 'org.robolectric:robolectric:3.+' //核心包
testCompile 'org.robolectric:shadows-support-v4:3.+' //支持Support-v4包
testCompile "org.robolectric:shadows-multidex:3.+" //支持Multidex功能
//...
}
</pre></div>
<p>  值得注意的是,要使用Robolectric也要添加JUnit依赖,具体可以回顾一下<a href="https://maxwell-nc.github.io/android/junitTest.html">《Android单元测试之JUnit框架》</a>。</p>
<h2>测试运行环境(@RunWith)</h2>
<p>  还记之前文章说到的JUnit给我们提供一个<code>@RunWith</code>注解去设置测试运行环境吗?Robolectric提供一个<code>RobolectricTestRunner</code>的沙盒测试运行环境,注意低版本的Robolectric可能不是这个类名。这个测试环境使用各种Shadow类代替真正的Android对象,从而实现模拟Android App的运行。所以所有需要使用Robolectric的测试类都要加上类注解:</p>
<div class="highlight"><pre><span></span><span class="nd">@RunWith</span><span class="o">(</span><span class="n">RobolectricTestRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtilsTest</span> <span class="o">{</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<h2>Robolectric配置(@Config)</h2>
<p>  很多网上的教程都是直接给了一个简单的例子,结果自己运行的时候会发现出现各种问题,所以这里先不给例子,先说一下怎么配置才能正确地运行。</p>
<p>  为了方便配置RobolectricTestRunner提供的环境,比如要设置运行的SDK版本,设置包名,自定义Application等等配置,Robolectric提供了一个<code>@Config</code>注解方便用户配置Robolectric。我们可以从源码中看到@Config可以接受很多参数,下面是几个比较常用的:</p>
<ul>
<li><strong>sdk:</strong>SDK版本</li>
<li><strong>manifest:</strong>清单文件位置</li>
<li><strong>buildDir:</strong>构建目录</li>
<li><strong>packageName:</strong>包名</li>
<li><strong>constants:</strong>常量设置(一般直接使用BuildConfig)</li>
<li><strong>shadows:</strong>自定义Shadow类</li>
<li><strong>application:</strong>自定义Application类</li>
</ul>
<p>  由于有很多参数,Robolectric为了使用方便提供了很多默认值,通常唯一必须指定的只有constants,因为配置BuildConfig后Robolectric框架会自动完成寻找各种目录和配置包名等等操作。所以需要在使用Robolectric的测试类上加上@Config注解:</p>
<div class="highlight"><pre><span></span><span class="nd">@RunWith</span><span class="o">(</span><span class="n">RobolectricTestRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Config</span><span class="o">(</span><span class="n">constants</span> <span class="o">=</span> <span class="n">BuildConfig</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtilsTest</span> <span class="o">{</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<p>  好了,按照之前说的,配置BuildConfig后就会自动完成寻找各种目录和配置包名等等操作,唯独这个AndroidManifest清单文件可能会寻找不到。</p>
<h2>AndroidManifest问题</h2>
<p>  如果你按照其他的教程来操作,很可能运行时就提示:<code>No such manifest file: build\intermediates\bundles\debug\AndroidManifest.xml</code>,无法找到AndroidManifest.xml。我们来分析分析这个问题。首先由于@Config最终是给RobolectricTestRunner使用的,所以我们打开RobolectricTestRunner的源码,可以找到:</p>
<div class="highlight"><pre><span></span><span class="kd">protected</span> <span class="n">List</span><span class="o"><</span><span class="n">FrameworkMethod</span><span class="o">></span> <span class="nf">getChildren</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">//...</span>
<span class="n">Config</span> <span class="n">config</span> <span class="o">=</span> <span class="n">getConfig</span><span class="o">(</span><span class="n">frameworkMethod</span><span class="o">.</span><span class="na">getMethod</span><span class="o">());</span>
<span class="n">AndroidManifest</span> <span class="n">appManifest</span> <span class="o">=</span> <span class="n">getAppManifest</span><span class="o">(</span><span class="n">config</span><span class="o">);</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<p>  当然我们找到getAppManifest方法发现它采用了ManifestFactory工厂去生产清单文件,找到工厂接口实现类GradleManifestFactory是适合于Android的清单工厂。可以看到里面各种自动寻找目录的逻辑,其中:</p>
<div class="highlight"><pre><span></span><span class="n">String</span> <span class="n">manifestName</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">manifest</span><span class="o">();</span>
<span class="n">URL</span> <span class="n">manifestUrl</span> <span class="o">=</span> <span class="n">getClass</span><span class="o">().</span><span class="na">getClassLoader</span><span class="o">().</span><span class="na">getResource</span><span class="o">(</span><span class="n">manifestName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">manifestUrl</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">manifestUrl</span><span class="o">.</span><span class="na">getProtocol</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"file"</span><span class="o">))</span> <span class="o">{</span>
<span class="n">manifest</span> <span class="o">=</span> <span class="n">FileFsFile</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">manifestUrl</span><span class="o">.</span><span class="na">getPath</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">FileFsFile</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">buildOutputDir</span><span class="o">,</span> <span class="s">"manifests"</span><span class="o">,</span> <span class="s">"full"</span><span class="o">).</span><span class="na">exists</span><span class="o">())</span> <span class="o">{</span>
<span class="n">manifest</span> <span class="o">=</span> <span class="n">FileFsFile</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">buildOutputDir</span><span class="o">,</span> <span class="s">"manifests"</span><span class="o">,</span> <span class="s">"full"</span><span class="o">,</span> <span class="n">flavor</span><span class="o">,</span> <span class="n">abiSplit</span><span class="o">,</span> <span class="n">type</span><span class="o">,</span> <span class="n">manifestName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">FileFsFile</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">buildOutputDir</span><span class="o">,</span> <span class="s">"manifests"</span><span class="o">,</span> <span class="s">"aapt"</span><span class="o">).</span><span class="na">exists</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// Android gradle plugin 2.2.0+ can put library manifest files inside of "aapt" instead of "full"</span>
<span class="n">manifest</span> <span class="o">=</span> <span class="n">FileFsFile</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">buildOutputDir</span><span class="o">,</span> <span class="s">"manifests"</span><span class="o">,</span> <span class="s">"aapt"</span><span class="o">,</span> <span class="n">flavor</span><span class="o">,</span> <span class="n">abiSplit</span><span class="o">,</span> <span class="n">type</span><span class="o">,</span> <span class="n">manifestName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">manifest</span> <span class="o">=</span> <span class="n">FileFsFile</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">buildOutputDir</span><span class="o">,</span> <span class="s">"bundles"</span><span class="o">,</span> <span class="n">flavor</span><span class="o">,</span> <span class="n">abiSplit</span><span class="o">,</span> <span class="n">type</span><span class="o">,</span> <span class="n">manifestName</span><span class="o">);</span>
<span class="o">}</span>
</pre></div>
<p>  这部分代码就是寻找清单文件的代码,为了偷懒,直接下断点,看看生成的路径是否正确:
<img alt="path" src="../images/robolectricTest/1.jpg">
  可以看到获取完整路径后实际上是少了一个模块名称,所以会走到最后的逻辑,从而导致提示报错。比较麻烦的时候,这个工厂我们不能自定义,退而求之我们修改他的前序buildOutputDir参数,这个参数就是对应Config参数buildDir,所以给Config增加一个参数:</p>
<div class="highlight"><pre><span></span><span class="nd">@RunWith</span><span class="o">(</span><span class="n">RobolectricTestRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Config</span><span class="o">(</span><span class="n">constants</span> <span class="o">=</span> <span class="n">BuildConfig</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
<span class="n">buildDir</span> <span class="o">=</span> <span class="s">"app/build"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtilsTest</span> <span class="o">{</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<p>  注意相对和绝对路径问题。</p>
<h2>Robolectric运行测试</h2>
<p>  下面我们尝试些一个测试例子,创建一个Activity并且验证它非null:</p>
<div class="highlight"><pre><span></span><span class="nd">@RunWith</span><span class="o">(</span><span class="n">RobolectricTestRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Config</span><span class="o">(</span><span class="n">constants</span> <span class="o">=</span> <span class="n">BuildConfig</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
<span class="n">buildDir</span> <span class="o">=</span> <span class="s">"app/build"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtilsTest</span> <span class="o">{</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testActivity</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">MainActivity</span> <span class="n">mainActivity</span> <span class="o">=</span> <span class="n">Robolectric</span><span class="o">.</span><span class="na">setupActivity</span><span class="o">(</span><span class="n">MainActivity</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">Assert</span><span class="o">.</span><span class="na">assertNotNull</span><span class="o">(</span><span class="n">mainActivity</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  这个简单的例子我们先不解析为什么这么写,尝试运行,你可能会遇到以下问题:
- AppCompatActivity问题
- MultiDexApplication问题</p>
<p>  遇到上述问题请先跳到后面看对应的问题章节,如果你无上述问题,但是运行时一直Download,如下图所示:
<img alt="download" src="../images/robolectricTest/2.jpg">
  这个是Robolectric执行不同SDK版本运行时需要对应运行库,由于服务器本身比较慢,经常超时,有可能一直卡住,如果你足够耐心可以等待其下载完成,如果你没有耐心,可以先跳过,看后面的“Robolectric依赖库问题”。</p>
<p>  如果上述问题都处理了,就可以看到运行通过了。接下来我们来正式学习Robolectric的用法。</p>
<h2>Robolectric依赖库问题</h2>
<p>  由于Robolectric的依赖库下载经常超时,我们可以改用手动下载方式去解决,先找到<code>C:\Users\(你的用户名)\.m2\repository\org\robolectric\android-all\</code>目录为需要下载的依赖库位置,可以Maven参考去下载对应版本:<a href="http://mvnrepository.com/artifact/org.robolectric/android-all">http://mvnrepository.com/artifact/org.robolectric/android-all</a>,下载的Jar后先暂停测试进程,然后删除对应的xxx.jar.tmp文件,复制xxx.jar文件进去,重新运行测试即可。</p>
<h2>AppCompatActivity问题</h2>
<p>  如果你使用的Activity是继承自AppCompatActivity,运行的时候会出现<code>java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.</code>问题,遇到这个问题只需要把继承AppCompatActivity的主题改为Theme.Appcompat主题或者他的子主题,比如:</p>
<div class="highlight"><pre><span></span><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".MainActivity"</span>
<span class="na">android:theme=</span><span class="s">"@style/Theme.AppCompat.Light"</span><span class="nt">></span>
//....
<span class="nt"></activity></span>
</pre></div>
<h2>MultiDexApplication问题</h2>
<p>  如果你的Application继承自MultiDexApplication就有可能会出现:<code>java.lang.RuntimeException: Multi dex installation failed.</code>,那是因为你没有添加shadows-multidex依赖库,可以参考前文说的,添加:</p>
<div class="highlight"><pre><span></span>testCompile "org.robolectric:shadows-multidex:3.+"
</pre></div>
<h2>测试Activity</h2>
<p>  上面的例子已经展示了通过<code>Robolectric.setupActivity</code>创建了一个Activity,那么这个setupActivity做了什么呢?下面我们看一下源码,可以看出,实际上setupActivity相当于做了:</p>
<div class="highlight"><pre><span></span><span class="n">Robolectric</span><span class="o">.</span><span class="na">buildActivity</span><span class="o">(</span><span class="n">MainActivity</span><span class="o">.</span><span class="na">class</span><span class="o">).</span><span class="na">create</span><span class="o">().</span><span class="na">get</span><span class="o">();</span>
</pre></div>
<p>  在Robolectric中,Activity的生命周期由ActivityController来控制,使用buildActivity来创建一个ActivityController,通过查看源码可以看到,调用create()方法,实际上顺序调用了performCreate的方法,实际上就是执行了Activity的onCreate方法。而get()则是获取Activity对象。通过查看ActivityController的源码,可以看出他身上的方法和常用Activity的生命周期对应如下:</p>
<ul>
<li><strong>create()</strong>-->Activity.onCreate()</li>
<li><strong>start()</strong>-->Activity.onStart()</li>
<li><strong>resume()</strong>-->Activity.onResume()</li>
<li><strong>pause()</strong>-->Activity.onPause()</li>
<li><strong>stop()</strong>-->Activity.onStop()</li>
<li><strong>destroy()</strong>-->Activity.onDestory()</li>
</ul>
<p>  还有其他比如onRestart等的可以参考源代码找到。</p>
<h2>测试Intent</h2>
<p>  假设有一个MainActivity,上面的btnNext按钮点击后会跳转到NextActivity,我们利用Robolectric来测试这段代码:</p>
<div class="highlight"><pre><span></span><span class="c1">//模拟点击跳转</span>
<span class="n">Button</span> <span class="n">btnNext</span> <span class="o">=</span> <span class="o">(</span><span class="n">Button</span><span class="o">)</span> <span class="n">mainActivity</span><span class="o">.</span><span class="na">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">btn_next</span><span class="o">);</span>
<span class="n">btnNext</span><span class="o">.</span><span class="na">performClick</span><span class="o">();</span>
<span class="c1">//获取跳转的意图</span>
<span class="n">Intent</span> <span class="n">actual</span> <span class="o">=</span> <span class="n">ShadowApplication</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getNextStartedActivity</span><span class="o">();</span>
<span class="c1">//期望意图</span>
<span class="n">Intent</span> <span class="n">expected</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Intent</span><span class="o">(</span><span class="n">mainActivity</span><span class="o">,</span> <span class="n">NextActivity</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="c1">//假设一致</span>
<span class="n">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="n">expected</span><span class="o">.</span><span class="na">getComponent</span><span class="o">(),</span> <span class="n">actual</span><span class="o">.</span><span class="na">getComponent</span><span class="o">());</span>
</pre></div>
<p>  值得注意的是,如果新版Robolectric使用Assert.assertEquals(expected, actual)来直接对比两个Intent,可能会出现不一致现象。所以只能对比他的组件名,后面会介绍另外一个工具帮助我们快速对比。</p>
<h2>测试Fragment</h2>
<p>  Fragment和Activity的测试大同小异,值得注意的是如果使用的兼容包要注意导入,代码如下:</p>
<div class="highlight"><pre><span></span><span class="c1">//非V4包写法</span>
<span class="n">BlankFragment</span> <span class="n">blankFragment</span> <span class="o">=</span> <span class="n">Robolectric</span><span class="o">.</span><span class="na">buildFragment</span><span class="o">(</span><span class="n">BlankFragment</span><span class="o">.</span><span class="na">class</span><span class="o">).</span><span class="na">get</span><span class="o">();</span>
<span class="c1">//兼容V4包写法</span>
<span class="n">SuppportFragment</span> <span class="n">supportFragment</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SuppportFragment</span><span class="o">();</span>
<span class="n">SupportFragmentTestUtil</span><span class="o">.</span><span class="na">startFragment</span><span class="o">(</span><span class="n">supportFragment</span><span class="o">);</span><span class="c1">//触发Fragment的onCreateView()</span>
</pre></div>
<p>  非V4包buildFragment产生的也是FragmentController,和Activity的结构大体相似,而V4包的则是FragmentManager,这里不一一分析了。</p>
<p>  另外还有测试Service等组件,可以使用对应的buildXxx,比如说使用<code>Robolectric.buildService(Service.class)</code>来获得ServiceController,剩下的逻辑和上述的测试Activity的大体相同。如果不关心生命周期,可以把组件当做普通类使用测试(不建议)。</p>
<h2>测试Toast</h2>
<p>  这个看上去似乎没有什么必要的工作,实际上这里是想说明一种测试思想。</p>
<div class="highlight"><pre><span></span><span class="c1">//...</span>
<span class="c1">//上面执行了弹出Toast的代码</span>
<span class="n">assertEquals</span><span class="o">(</span><span class="n">ShadowToast</span><span class="o">.</span><span class="na">getTextOfLatestToast</span><span class="o">(),</span><span class="s">"toast content"</span><span class="o">);</span>
</pre></div>
<p>  这里举这个简单例子是为了简单说明,如果要获取某个类的状态,可以通过其Shadow类来获取,比如AlertDialog可以通过ShadowAlertDialog来获取弹出的AlertDialog等等。这里就不一一说明了。</p>
<h2>Application和ShadowApplication</h2>
<p>  <code>ShadowApplication.getInstance()</code>和<code>RuntimeEnvironment.application</code>两个Application有什么区别呢?我们分析源码看看ShadowApplication.getInstance()的源码如下:</p>
<div class="highlight"><pre><span></span><span class="n">RuntimeEnvironment</span><span class="o">.</span><span class="na">application</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="n">shadowOf</span><span class="o">(</span><span class="n">RuntimeEnvironment</span><span class="o">.</span><span class="na">application</span><span class="o">)</span>
</pre></div>
<p>  其中shadowOf是把真实模拟的Application变成Shadow对象,可以提供一些原本没有的方法。例如RuntimeEnvironment.application可以使用getString去获取字符串信息,而ShadowApplication.getInstance()不行,但他可以使用getNextStartedActivity获取下一个启动的Activity等方法。</p>
<h2>获取Shadow对象</h2>
<p>  假设有一个对象,你想获取它的Shadow对象,可以使用<code>Shadows.shadowOf</code>,例如上文说到的:</p>
<div class="highlight"><pre><span></span> <span class="n">ShadowApplication</span> <span class="n">shadowApplication</span> <span class="o">=</span> <span class="n">Shadows</span><span class="o">.</span><span class="na">shadowOf</span><span class="o">(</span><span class="n">RuntimeEnvironment</span><span class="o">.</span><span class="na">application</span><span class="o">);</span>
</pre></div>
<p>  如果是自定义的Shadow对象则使用<code>Shadow.extract</code>方法,别急,马上就说明怎么自定义Shadow对象。</p>
<h2>自定义Shadow对象</h2>
<p>  假设有原始类SampleClass,你想要创建他的Shadow对象,并且想修改和扩展它的方法,原始类代码如下:</p>
<div class="highlight"><pre><span></span><span class="cm">/**</span>
<span class="cm"> * 原始类</span>
<span class="cm"> */</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleClass</span> <span class="o">{</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">getString</span><span class="o">(</span><span class="n">String</span> <span class="n">str</span><span class="o">){</span>
<span class="k">return</span> <span class="n">str</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  你可以使用Robolectric提供给的@Implements注解说明原始类,使用@Implementation说明该方法为替换原始类中的方法,另外可以随意扩展方法,代码如下:</p>
<div class="highlight"><pre><span></span><span class="nd">@Implements</span><span class="o">(</span><span class="n">SampleClass</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ShadowSampleClass</span> <span class="o">{</span>
<span class="nd">@Implementation</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">getString</span><span class="o">(</span><span class="n">String</span> <span class="n">str</span><span class="o">){</span>
<span class="k">return</span> <span class="s">"test"</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**</span>
<span class="cm"> * 扩展的方法</span>
<span class="cm"> */</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">getStringEx</span><span class="o">(){</span>
<span class="k">return</span> <span class="s">"test"</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  之后你需要@Config下添加shadows参数说明需要使用的Shadow对象,就可以在代码中使用了,运行的时候单元测试中的SampleClass会被替换成ShadowSampleClass,具体代码如下:</p>
<div class="highlight"><pre><span></span><span class="nd">@RunWith</span><span class="o">(</span><span class="n">RobolectricTestRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Config</span><span class="o">(</span><span class="n">constants</span> <span class="o">=</span> <span class="n">BuildConfig</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
<span class="n">buildDir</span> <span class="o">=</span> <span class="s">"app/build"</span><span class="o">,</span>
<span class="n">shadows</span> <span class="o">=</span> <span class="o">{</span><span class="n">ShadowSampleClass</span><span class="o">.</span><span class="na">class</span><span class="o">})</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtilsTest</span> <span class="o">{</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testShadows</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">SampleClass</span> <span class="n">sampleClass</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SampleClass</span><span class="o">();</span>
<span class="n">String</span> <span class="n">original</span> <span class="o">=</span> <span class="n">sampleClass</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"123"</span><span class="o">);</span>
<span class="n">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span><span class="n">original</span><span class="o">);</span>
<span class="c1">//转换出Shadow对象</span>
<span class="n">ShadowSampleClass</span> <span class="n">shadowSampleClass</span> <span class="o">=</span> <span class="n">Shadow</span><span class="o">.</span><span class="na">extract</span><span class="o">(</span><span class="n">sampleClass</span><span class="o">);</span>
<span class="n">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span><span class="n">shadowSampleClass</span><span class="o">.</span><span class="na">getStringEx</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  可以发现上述两个assertEquals都是passed的。</p>
<h2>总结</h2>
<p>  Robolectric给我们带来了非常方便的测试,但是它本身存在比较多的坑,而且版本之间差别也非常大,所以这个玩意还需要大家多琢磨琢磨,尤其是在已有项目中集成可是要费一点时间了。好了,还记得上文说到的Intent不能使用Assert直接判断是否相等吗?下篇给大家介绍一个神器来解决这个问题。</p>Android单元测试之Mockito框架2017-05-25T00:00:00+08:002017-05-25T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-25:/android/mockitoTest.html<h2>前序</h2>
<p>  之前写的<a href="https://maxwell-nc.github.io/android/junitTest.html">JUnit框架单元测试</a>的最后留了一个悬念,今天我们把这个问题解决下,首先我们要理解mock的概念,然后学习使用mock来做单元测试。关于Mock的框架有很多,比如Mockito、PowerMock、EasyMock等等,本文主要介绍Mockito的用法,各种框架的对比不在本文阐述范围,而且此类框架大体相同,只需要学习其中一个就能轻松地学习其他框架,没必要纠结那个框架才是最好的。</p>
<h2>Mock的概念</h2>
<p>  首先要明白什么是Mock,为什么要Mock,Mock能干什么这三个问题。Mock的中文意思是“模仿”,Mock就是去构造(模仿)一个虚拟的对象,而这个对象通常比较难直接创建,有了Mock可以轻松地帮助你对复杂的功能解耦,实现单元测试。比如<a href="https://maxwell-nc.github.io/android/junitTest.html">前文</a>最后留下的Log类,你会发现它依赖于Android运行环境,很难把整个依赖树都构建出来,所以我们需要Mock。</p>
<h2>集成Mocktio</h2>
<p>  Android上集成非常简单,在app项目下的build.gradle添加测试编译依赖(下面采用2.x最新版本):</p>
<div class="highlight"><pre><span></span>dependencies {
//...
testCompile "org.mockito:mockito-core:2.+"
}
</pre></div>
<p>  然后使用Gradle Sync一下即可。顺便附上Mockito的Jcenter地址:<a href="http://jcenter.bintray.com/org/mockito/mockito-core/">http …</a></p><h2>前序</h2>
<p>  之前写的<a href="https://maxwell-nc.github.io/android/junitTest.html">JUnit框架单元测试</a>的最后留了一个悬念,今天我们把这个问题解决下,首先我们要理解mock的概念,然后学习使用mock来做单元测试。关于Mock的框架有很多,比如Mockito、PowerMock、EasyMock等等,本文主要介绍Mockito的用法,各种框架的对比不在本文阐述范围,而且此类框架大体相同,只需要学习其中一个就能轻松地学习其他框架,没必要纠结那个框架才是最好的。</p>
<h2>Mock的概念</h2>
<p>  首先要明白什么是Mock,为什么要Mock,Mock能干什么这三个问题。Mock的中文意思是“模仿”,Mock就是去构造(模仿)一个虚拟的对象,而这个对象通常比较难直接创建,有了Mock可以轻松地帮助你对复杂的功能解耦,实现单元测试。比如<a href="https://maxwell-nc.github.io/android/junitTest.html">前文</a>最后留下的Log类,你会发现它依赖于Android运行环境,很难把整个依赖树都构建出来,所以我们需要Mock。</p>
<h2>集成Mocktio</h2>
<p>  Android上集成非常简单,在app项目下的build.gradle添加测试编译依赖(下面采用2.x最新版本):</p>
<div class="highlight"><pre><span></span>dependencies {
//...
testCompile "org.mockito:mockito-core:2.+"
}
</pre></div>
<p>  然后使用Gradle Sync一下即可。顺便附上Mockito的Jcenter地址:<a href="http://jcenter.bintray.com/org/mockito/mockito-core/">http://jcenter.bintray.com/org/mockito/mockito-core/</a></p>
<h2>验证互动(Interactions)</h2>
<p>  下面来使用Mockito验证互动功能,比如说验证TextView的setText方法交互情况:</p>
<div class="highlight"><pre><span></span><span class="n">TextView</span> <span class="n">mockedTextView</span> <span class="o">=</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">mock</span><span class="o">(</span><span class="n">TextView</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">mockedTextView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">).</span><span class="na">setText</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
</pre></div>
<p>  上述代码的<code>mock</code>方法用于“模仿”一个对象并返回这个对象,而<code>verify</code>方法则是用于验证“模仿对象”的互动。<strong>特别注意:如果你使用<code>mockedTextView.getText()</code>获取设置的值会发现返回值为null</strong></p>
<h2>设置桩(Stub)</h2>
<p>  上面最后说到<code>mockedTextView.getText()</code>会返回一个null,假设我们需要测试<code>mockedTextView.getText()</code>返回值是否正确怎么处理呢?Mockito给我们设置方法桩功能。简单来说就是“指定方法返回的结果”,比如下面代码:</p>
<div class="highlight"><pre><span></span><span class="n">TextView</span> <span class="n">mockedTextView</span> <span class="o">=</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">mock</span><span class="o">(</span><span class="n">TextView</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">when</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">.</span><span class="na">getText</span><span class="o">()).</span><span class="na">thenReturn</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">.</span><span class="na">getText</span><span class="o">());</span>
</pre></div>
<p>  上述代码用<code>when</code>方法指定要设置桩的方法,<code>thenReturn</code>来指定返回值,测试后发现输出为“test”,表明设置方法桩成功。值得注意的时候,给方法设置桩可以设置多次,只会返回最后一次设置的值。</p>
<p>  除了when...thenReturn的写法外,还有doRetrun...when的写法,代码如下:</p>
<div class="highlight"><pre><span></span><span class="n">Mockito</span><span class="o">.</span><span class="na">doReturn</span><span class="o">(</span><span class="s">"123"</span><span class="o">).</span><span class="na">when</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">).</span><span class="na">getText</span><span class="o">();</span>
</pre></div>
<h2>验证模式(Verification Mode)</h2>
<p>  细心的读者会发现<code>verify</code>方法重载里另外一个带VerificationMode参数的方法。这个参数用于设置验证模式,比如说需要验证方法执行了多少次。</p>
<p>  VerificationMode是一个接口,我们可以看看源码中实现这个接口的类来学习它的用法:
<img alt="impl" src="../images/mockitoTest/1.jpg">
  比如Mockito.times(1)代表验证方法执行了1次:</p>
<div class="highlight"><pre><span></span><span class="n">Mockito</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">,</span><span class="n">Mockito</span><span class="o">.</span><span class="na">times</span><span class="o">(</span><span class="mi">1</span><span class="o">)).</span><span class="na">getText</span><span class="o">();</span>
</pre></div>
<p>  其他的验证模式也是大体相同的用法,具体可以参考类的说明。</p>
<p>  <strong>注意默认没有验证模式的verify方法使用的默认验证模式就是</strong><code>Mockito.times(1)</code>。</p>
<h2>参数匹配器(Argument Matcher)</h2>
<p>  有时候我们不关心输入,比如说setText()方法:</p>
<div class="highlight"><pre><span></span><span class="n">TextView</span> <span class="n">mockedTextView</span> <span class="o">=</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">mock</span><span class="o">(</span><span class="n">TextView</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">mockedTextView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">).</span><span class="na">setText</span><span class="o">(</span><span class="n">Mockito</span><span class="o">.</span><span class="na">anyString</span><span class="o">());</span>
</pre></div>
<p>  上述的<code>Mockito.anyString()</code>就是一个参数匹配器,值得注意的是,默认的验证模式是<code>Mockito.times(1)</code>,如果使用了参数匹配器,注意调用次数,否则回报:<code>org.mockito.exceptions.verification.TooManyActualInvocations</code>,下面是调用两次的正确例子:</p>
<div class="highlight"><pre><span></span><span class="n">TextView</span> <span class="n">mockedTextView</span> <span class="o">=</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">mock</span><span class="o">(</span><span class="n">TextView</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">mockedTextView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"test"</span><span class="o">);</span>
<span class="n">mockedTextView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">"abc"</span><span class="o">);</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">,</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">times</span><span class="o">(</span><span class="mi">2</span><span class="o">)).</span><span class="na">setText</span><span class="o">(</span><span class="n">Mockito</span><span class="o">.</span><span class="na">anyString</span><span class="o">());</span>
</pre></div>
<p>  由于参数匹配器的种类太多,这里就不一样列举了,可以参考ArgumentMatchers类找到各种匹配器。</p>
<h2>抛出异常</h2>
<p>  若果需要某个方法抛出异常,可以使用下面的方法:</p>
<div class="highlight"><pre><span></span><span class="c1">//void返回方法</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">doThrow</span><span class="o">(</span><span class="k">new</span> <span class="n">RuntimeException</span><span class="o">()).</span><span class="na">when</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">).</span><span class="na">setText</span><span class="o">(</span><span class="s">"abc"</span><span class="o">);</span>
<span class="c1">//非void返回方法</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">when</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">.</span><span class="na">getText</span><span class="o">()).</span><span class="na">thenThrow</span><span class="o">(</span><span class="k">new</span> <span class="n">RuntimeException</span><span class="o">());</span>
</pre></div>
<p>  其中注意区分不同返回类型的写法不同。另外如果需要防止异常中断执行,可以在增加一个doNothing方法,代码如下:</p>
<div class="highlight"><pre><span></span><span class="n">Mockito</span><span class="o">.</span><span class="na">doNothing</span><span class="o">().</span><span class="na">doThrow</span><span class="o">(</span><span class="k">new</span> <span class="n">NullPointerException</span><span class="o">()).</span><span class="na">when</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">).</span><span class="na">setText</span><span class="o">(</span><span class="s">"abc"</span><span class="o">);</span>
</pre></div>
<p>  可以看到上述代码,<strong>只有Void返回类型方法</strong>才能使用<code>doNothing()</code></p>
<h2>自定义应答(Answer)</h2>
<p>  对于一个方法设置桩when...thenXxx或者doXxxx...when的组合外,Mockito给了一个自定义应答的的方法让我们自定义方法应答的内容。试想一下,假设有一个异步方法(当然返回类型就是Void)的回调中有多个回调,当你想指定执行某个回调之前学到的显然就不那么容易实现了。如果自定义Answer内容,那将是非常简单的,示例代码如下:</p>
<div class="highlight"><pre><span></span><span class="n">Mockito</span><span class="o">.</span><span class="na">doAnswer</span><span class="o">(</span><span class="k">new</span> <span class="n">Answer</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">Object</span> <span class="nf">answer</span><span class="o">(</span><span class="n">InvocationOnMock</span> <span class="n">invocationOnMock</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Throwable</span> <span class="o">{</span>
<span class="c1">//获取第一个参数</span>
<span class="n">Object</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">invocationOnMock</span><span class="o">.</span><span class="na">getArgument</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="c1">//指定回调执行操作</span>
<span class="k">return</span> <span class="n">callback</span><span class="o">.</span><span class="na">onFinished</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}).</span><span class="na">when</span><span class="o">(</span><span class="n">mockedClass</span><span class="o">.</span><span class="na">asyncRequset</span><span class="o">(</span><span class="n">callback</span><span class="o">));</span><span class="c1">//执行一步操作</span>
</pre></div>
<p>  或者举一个简单的例子(采用when...thenAnswer方式):</p>
<div class="highlight"><pre><span></span><span class="n">Mockito</span><span class="o">.</span><span class="na">when</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">.</span><span class="na">getText</span><span class="o">()).</span><span class="na">thenAnswer</span><span class="o">(</span><span class="k">new</span> <span class="n">Answer</span><span class="o"><</span><span class="n">String</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">answer</span><span class="o">(</span><span class="n">InvocationOnMock</span> <span class="n">invocationOnMock</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Throwable</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"custom answer"</span><span class="o">);</span>
<span class="k">return</span> <span class="s">"test"</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">mockedTextView</span><span class="o">.</span><span class="na">getText</span><span class="o">());</span>
</pre></div>
<p>  很明显,这里最终输出为:</p>
<div class="highlight"><pre><span></span>custom answer
test
</pre></div>
<h2>间谍(Spy)</h2>
<p>  要知道如果Mock一个对象后,这个<strong>Mock对象对于所有非Void返回方法将返回默认值(对象则返回null),所有Void方法将什么都不做</strong>。如果要保留原来对象的功能,而仅仅修改一个或几个方法的返回值,可以采用Spy方法,具体代码如下:</p>
<div class="highlight"><pre><span></span><span class="n">ArrayList</span> <span class="n">spyArray</span> <span class="o">=</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">spy</span><span class="o">(</span><span class="k">new</span> <span class="n">ArrayList</span><span class="o">());</span>
<span class="n">spyArray</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
<span class="n">spyArray</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">when</span><span class="o">(</span><span class="n">spyArray</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)).</span><span class="na">thenReturn</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span><span class="c1">//注意书写位置,否则报IndexOutOfBound</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">spyArray</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span><span class="c1">//输出1</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">spyArray</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span><span class="c1">//输出2</span>
</pre></div>
<p>  上述代码可以看到Spy方法没有改变ArrayList里的方法,只是当get(0)时返回1,其他方法执行逻辑还是ArrayList中的逻辑。</p>
<p>  特别注意这个Spy方法看上去似乎很方便,实际上如果你Spy一个需要Mock的对象,就会提示你该对象没有Mock,就比如TextView。</p>
<h2>Mock注解(Annotation)</h2>
<p>  使用@Mock可以帮我们快速Mock对象:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AnnotationTest</span> <span class="o">{</span>
<span class="nd">@Mock</span>
<span class="kd">private</span> <span class="n">TextView</span> <span class="n">mockedTextView</span><span class="o">;</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setup</span><span class="o">()</span> <span class="o">{</span>
<span class="n">MockitoAnnotations</span><span class="o">.</span><span class="na">initMocks</span><span class="o">(</span><span class="k">this</span><span class="o">);</span><span class="c1">//初始化所有Mock注解</span>
<span class="o">}</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<p>  如果觉得写setup方法比较麻烦,可以去掉并使用Mockito自带的JUnit Rule帮我们自动完成:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AnnotationTest</span> <span class="o">{</span>
<span class="nd">@Rule</span>
<span class="kd">public</span> <span class="n">MockitoRule</span> <span class="n">mockitoRule</span> <span class="o">=</span> <span class="n">MockitoJUnit</span><span class="o">.</span><span class="na">rule</span><span class="o">();</span>
<span class="nd">@Mock</span>
<span class="kd">private</span> <span class="n">TextView</span> <span class="n">mockedTextView</span><span class="o">;</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<p>  如果没有使用JUnit Runner,可以直接使用Mockito提供的JUnitRunner(Runner相当于一个容器,负责处理你的测试代码):</p>
<div class="highlight"><pre><span></span><span class="nd">@RunWith</span><span class="o">(</span><span class="n">MockitoJUnitRunner</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AnnotationTest</span> <span class="o">{</span>
<span class="nd">@Mock</span>
<span class="kd">private</span> <span class="n">TextView</span> <span class="n">mockedTextView</span><span class="o">;</span>
<span class="c1">//...</span>
<span class="o">}</span>
</pre></div>
<p>  另外Mockito还提供了其他注解,例如@Spy,这个可以用于<strong>无参构造</strong>的类初始化Spy,所以实用性并不高,其他一些不常用的这里就不介绍了。</p>
<h2>静态方法处理</h2>
<p>  实际上即使你看完前面全部内容,还是不能解决我们<a href="https://maxwell-nc.github.io/android/junitTest.html">上一篇文章</a>最后提到的那个问题,因为<strong>Mockito不支持静态方法的Mock</strong>!</p>
<p>  要Mock静态方法有两个方法,一个是使用PowerMock来扩展Mockito,另外一个就是创建一个StaticWrapper来把静态方法变成非静态方法,方法如下:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">LogTest</span> <span class="o">{</span>
<span class="kd">class</span> <span class="nc">StaticWrapper</span> <span class="o">{</span><span class="c1">//包裹静态方法为非静态方法</span>
<span class="kt">void</span> <span class="nf">i</span><span class="o">(</span><span class="n">String</span> <span class="n">tag</span><span class="o">,</span> <span class="n">String</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">tag</span><span class="o">,</span> <span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span>
<span class="n">StaticWrapper</span> <span class="n">mockedLog</span> <span class="o">=</span> <span class="n">Mockito</span><span class="o">.</span><span class="na">mock</span><span class="o">(</span><span class="n">StaticWrapper</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">mockedLog</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span> <span class="s">"test"</span><span class="o">);</span>
<span class="n">Mockito</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">mockedLog</span><span class="o">).</span><span class="na">i</span><span class="o">(</span><span class="s">"test"</span><span class="o">,</span> <span class="s">"test"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<h2>尾声</h2>
<p>  上文介绍了Mockito所有基本用法,但是这还不是Mockito的全部,感兴趣的可以自己深入研究一下,例如InOrder的用法等等。虽然我们已经学会了Mockito和JUnit,但是要在JVM上测试Android代码(比如要测试Activity的生命周期),显然之前学的无法解决这个问题。这里又留下一个悬念,下篇文章我们再来探讨这个问题。</p>快速集成ReactNative到现有Android项目2017-05-24T00:00:00+08:002017-05-24T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-24:/android/rnIntergation.html<h2>前序</h2>
<p>  突然想起来之前旧博客的<a href="http://blog.csdn.net/maxwell_nc/article/details/60960864">《Windows下搭建ReactNative开发环境(Android)》</a>留下的一个坑,遂填一下坑。而且集成ReactNative到现有Android项目也不是什么容易的事情,网上很多教程都是不完整,你会发现跟着来做都是一步一个坑,不断搜索error解决花了半天才能搭建好,下次再做又是一堆问题,所以我这里记录下自己的集成心得。</p>
<h2>准备操作</h2>
<p>  首先你建议你先看看<a href="http://blog.csdn.net/maxwell_nc/article/details/60960864">上一篇搭建环境的文章</a>,否则接下来的内容你可以看不懂或者没有对应的工具。然后就是你需要有一个现有的Android项目,如果你需要创建全新项目前一篇已经描述过了,本文就不再重复了。</p>
<p>  本文集成的<strong>ReactNative版本为0.44</strong>,如果不是的话可能与本文内容有所差异,请自行甄别。</p>
<h2>安装ReactNative到项目</h2>
<p>  我们已一个已存在的工程ExistedProject为例,首先打开项目的目录,在CMD输入:</p>
<div class="highlight"><pre><span></span>npm init
</pre></div>
<p>  <code>npm init</code>会提示引导你创建package.json,如下图所示:
<img alt="init" src="../images/rnIntergation/1.jpg">
  创建成功后可以安装React、ReactNative到目录里面,继续在<strong>当前目录</strong>下输入命令:</p>
<div class="highlight"><pre><span></span>npm install --save react react-native
</pre></div>
<p>  等待安装成功的过程中,可以到<a href="https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig">https://raw.githubusercontent …</a></p><h2>前序</h2>
<p>  突然想起来之前旧博客的<a href="http://blog.csdn.net/maxwell_nc/article/details/60960864">《Windows下搭建ReactNative开发环境(Android)》</a>留下的一个坑,遂填一下坑。而且集成ReactNative到现有Android项目也不是什么容易的事情,网上很多教程都是不完整,你会发现跟着来做都是一步一个坑,不断搜索error解决花了半天才能搭建好,下次再做又是一堆问题,所以我这里记录下自己的集成心得。</p>
<h2>准备操作</h2>
<p>  首先你建议你先看看<a href="http://blog.csdn.net/maxwell_nc/article/details/60960864">上一篇搭建环境的文章</a>,否则接下来的内容你可以看不懂或者没有对应的工具。然后就是你需要有一个现有的Android项目,如果你需要创建全新项目前一篇已经描述过了,本文就不再重复了。</p>
<p>  本文集成的<strong>ReactNative版本为0.44</strong>,如果不是的话可能与本文内容有所差异,请自行甄别。</p>
<h2>安装ReactNative到项目</h2>
<p>  我们已一个已存在的工程ExistedProject为例,首先打开项目的目录,在CMD输入:</p>
<div class="highlight"><pre><span></span>npm init
</pre></div>
<p>  <code>npm init</code>会提示引导你创建package.json,如下图所示:
<img alt="init" src="../images/rnIntergation/1.jpg">
  创建成功后可以安装React、ReactNative到目录里面,继续在<strong>当前目录</strong>下输入命令:</p>
<div class="highlight"><pre><span></span>npm install --save react react-native
</pre></div>
<p>  等待安装成功的过程中,可以到<a href="https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig">https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig</a>下载<code>.flowconfig</code>文件复制到项目之中。(这个文件只是用来约束Javascript代码,也可以跳过),很快就安装完成了:
<img alt="warn" src="../images/rnIntergation/2.jpg">
  这里需要有一个警告:</p>
<div class="highlight"><pre><span></span><span class="n">react</span><span class="o">-</span><span class="n">native</span><span class="mf">@0.44.0</span> <span class="n">requires</span> <span class="n">a</span> <span class="n">peer</span> <span class="n">of</span> <span class="n">react</span><span class="mf">@16.0.0</span><span class="o">-</span><span class="n">alpha</span><span class="mf">.6</span> <span class="n">but</span> <span class="n">none</span> <span class="n">was</span> <span class="n">installed</span>
</pre></div>
<p>  很多的文章都没有说明这个,导致后面项目运行后会出错。遇到这个问题是因为ReactNative和React的版本有着严格的对应关系,如果不一样就会报错,解决方法就是重新安装对应的React版本,输入命令:</p>
<div class="highlight"><pre><span></span><span class="n">npm</span> <span class="n">install</span> <span class="o">--</span><span class="n">save</span> <span class="n">react</span><span class="mf">@16.0.0</span><span class="o">-</span><span class="n">alpha</span><span class="mf">.6</span>
</pre></div>
<p>  这样就可以避免后面出现的这两个问题:</p>
<div class="highlight"><pre><span></span>Unable to resolve module `react/lib/ReactDebugCurrentFrame`
Unable to resolve module `react/lib/ReactComponentWithPureRenderMixin`
</pre></div>
<p>  接下来修改package.json文件,在<code>"scripts"</code>下增加一句<code>"start": "node node_modules/react-native/local-cli/cli.js start"</code>,注意json格式,需要添加逗号,最后package.json文件应该是这样的:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="nt">"name"</span><span class="p">:</span> <span class="s2">"ext_prj"</span><span class="p">,</span>
<span class="nt">"version"</span><span class="p">:</span> <span class="s2">"1.0.0"</span><span class="p">,</span>
<span class="nt">"description"</span><span class="p">:</span> <span class="s2">"nothing"</span><span class="p">,</span>
<span class="nt">"main"</span><span class="p">:</span> <span class="s2">"index.js"</span><span class="p">,</span>
<span class="nt">"scripts"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"start"</span><span class="p">:</span> <span class="s2">"node node_modules/react-native/local-cli/cli.js start"</span><span class="p">,</span>
<span class="nt">"test"</span><span class="p">:</span> <span class="s2">"echo \"Error: no test specified\" && exit 1"</span>
<span class="p">},</span>
<span class="nt">"author"</span><span class="p">:</span> <span class="s2">"maxwell-nc"</span><span class="p">,</span>
<span class="nt">"license"</span><span class="p">:</span> <span class="s2">"ISC"</span><span class="p">,</span>
<span class="nt">"dependencies"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"react"</span><span class="p">:</span> <span class="s2">"^16.0.0-alpha.6"</span><span class="p">,</span>
<span class="nt">"react-native"</span><span class="p">:</span> <span class="s2">"^0.44.0"</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2>创建安卓首页js文件</h2>
<p>  接下来在项目目录中创建index.android.js文件,然后编辑内容:</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'react'</span><span class="p">;</span>
<span class="kr">import</span> <span class="p">{</span> <span class="nx">AppRegistry</span><span class="p">,</span> <span class="nx">Text</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'react-native'</span><span class="p">;</span>
<span class="kr">class</span> <span class="nx">HelloWorld</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><</span><span class="nx">Text</span><span class="o">></span><span class="nx">Hello</span> <span class="nx">world</span><span class="o">!<</span><span class="err">/Text></span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">//这里的'HelloWorld'是后面android项目使用的</span>
<span class="c1">//而后面的HelloWorld是class的名称</span>
<span class="nx">AppRegistry</span><span class="p">.</span><span class="nx">registerComponent</span><span class="p">(</span><span class="s1">'HelloWorld'</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="nx">HelloWorld</span><span class="p">);</span>
</pre></div>
<p>  留意注释里面的内容,后面有用到,这个文件是react页面的启动页。基本的框架已经搭建好,下面我们配置android项目的设置。</p>
<h2>配置Android项目</h2>
<p>  首先在android项目目录下的build.gradle文件添加依赖Maven仓库,代码如下:</p>
<div class="highlight"><pre><span></span>allprojects {
repositories {
maven {
url "$rootDir/node_modules/react-native/android"
}
jcenter()
}
}
</pre></div>
<p>  这里又要注意了,node_modules目录的位置必须正确,否则Gradle就会报错误了。然后修改app目录下的build.gradle文件,添加ReactNative依赖:</p>
<div class="highlight"><pre><span></span>dependencies {
//...
compile "com.facebook.react:react-native:+"
}
</pre></div>
<p>  为了防止64位库问题和findbugsbug版本问题,同时增加下面的代码:</p>
<div class="highlight"><pre><span></span>android {
//...
defaultConfig {
//...
ndk{
abiFilters "armeabi-v7a","armeabi-v7a","x86"
}
}
//...
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.0'
}
}
</pre></div>
<p>  如果没有设置,后面可能会产生如下类似问题:</p>
<div class="highlight"><pre><span></span><span class="n">Error</span><span class="o">:</span><span class="n">Conflict</span> <span class="k">with</span> <span class="n">dependency</span> <span class="s1">'com.google.code.findbugs:jsr305'</span>
<span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">UnsatisfiedLinkError</span><span class="o">:</span> <span class="n">could</span> <span class="n">find</span> <span class="n">DSO</span> <span class="n">to</span> <span class="n">load</span><span class="o">:</span> <span class="n">libreactnativejni</span><span class="o">.</span><span class="na">so</span>
<span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">UnsatisfiedLinkError</span><span class="o">:</span> <span class="n">dlopen</span> <span class="n">failed</span><span class="o">:</span> <span class="s2">"xxx/libgnustl_shared.so"</span> <span class="k">is</span> <span class="mi">32</span><span class="o">-</span><span class="n">bit</span> <span class="n">instead</span> <span class="n">of</span> <span class="mi">64</span><span class="o">-</span><span class="n">bit</span>
</pre></div>
<p>  配置完Gradle之后执行以下Sync操作确保上述操作无误,然后给AndroidManifest.xml文件添加上网路权限,和DevSettingsActivity界面配置:</p>
<div class="highlight"><pre><span></span><span class="nt"><uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.INTERNET"</span> <span class="nt">/></span>
<span class="nt"><activity</span> <span class="na">android:name=</span><span class="s">"com.facebook.react.devsupport.DevSettingsActivity"</span> <span class="nt">/></span>
</pre></div>
<p>  DevSettingsActivity这个界面在Release的时候可以去掉,仅仅用于开发测试设置。</p>
<h2>Andorid调用ReactNative</h2>
<p>  首先需要创建一个基类,方便后面使用,这里给大家写了一个BaseReactActivity:</p>
<div class="highlight"><pre><span></span><span class="cm">/**</span>
<span class="cm"> * ReactNativeActivity基类</span>
<span class="cm"> */</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">BaseReactActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="kd">implements</span> <span class="n">DefaultHardwareBackBtnHandler</span> <span class="o">{</span>
<span class="kd">protected</span> <span class="n">ReactRootView</span> <span class="n">mReactRootView</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">ReactInstanceManager</span> <span class="n">mReactInstanceManager</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">mReactInstanceManager</span> <span class="o">=</span> <span class="n">ReactInstanceManager</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">setApplication</span><span class="o">(</span><span class="n">getApplication</span><span class="o">())</span>
<span class="o">.</span><span class="na">setBundleAssetName</span><span class="o">(</span><span class="s">"index.android.bundle"</span><span class="o">)</span>
<span class="o">.</span><span class="na">setJSMainModuleName</span><span class="o">(</span><span class="s">"index.android"</span><span class="o">)</span>
<span class="o">.</span><span class="na">addPackage</span><span class="o">(</span><span class="k">new</span> <span class="n">MainReactPackage</span><span class="o">())</span>
<span class="o">.</span><span class="na">setUseDeveloperSupport</span><span class="o">(</span><span class="n">BuildConfig</span><span class="o">.</span><span class="na">DEBUG</span><span class="o">)</span>
<span class="o">.</span><span class="na">setInitialLifecycleState</span><span class="o">(</span><span class="n">LifecycleState</span><span class="o">.</span><span class="na">RESUMED</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="cm">/**</span>
<span class="cm"> * 加载ReactNative内容</span>
<span class="cm"> */</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">loadReact</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">//这里的HelloWorld必须对应“index.android.js”中的“AppRegistry.registerComponent()”的第一个参数</span>
<span class="n">mReactRootView</span><span class="o">.</span><span class="na">startReactApplication</span><span class="o">(</span><span class="n">mReactInstanceManager</span><span class="o">,</span> <span class="s">"HelloWorld"</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">invokeDefaultOnBackPressed</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onBackPressed</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onPause</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onPause</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mReactInstanceManager</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mReactInstanceManager</span><span class="o">.</span><span class="na">onHostPause</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onResume</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onResume</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mReactInstanceManager</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mReactInstanceManager</span><span class="o">.</span><span class="na">onHostResume</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDestroy</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onDestroy</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mReactInstanceManager</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mReactInstanceManager</span><span class="o">.</span><span class="na">onHostDestroy</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onBackPressed</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mReactInstanceManager</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mReactInstanceManager</span><span class="o">.</span><span class="na">onBackPressed</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onBackPressed</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  注意代码中startReactApplication方法中的参数要与前面index.android.js文件中设置的一样(看注释),然后根据官方的文档,这个ReactInstanceManager可以设置成一个单例,全局使用同一个ReactInstanceManager就可以了,这里为了方便快捷就写在一起了。这个类主要传递了声明周期给ReactNative,也处理了后退按钮事件。</p>
<p>  注意上面的类中BuildConfig的导包是选择自己的包名,而不是其他:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">com.github.maxwell.nc.existedproject.BuildConfig</span><span class="o">;</span>
</pre></div>
<p>  下面需要把使用到ReactNative的Activity继承这个Activity,如果需要这个ContentView使用ReactRootView,可以在onCreate中添加:</p>
<div class="highlight"><pre><span></span><span class="n">setContentView</span><span class="o">(</span><span class="n">mReactRootView</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ReactRootView</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>
</pre></div>
<p>  如果是在布局中增加了ReactRootView,可以使用:</p>
<div class="highlight"><pre><span></span><span class="n">setContentView</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">activity_main</span><span class="o">);</span>
<span class="n">mReactRootView</span> <span class="o">=</span> <span class="o">(</span><span class="n">ReactRootView</span><span class="o">)</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">rrv_content</span><span class="o">);</span>
</pre></div>
<p>  然后可以调用<code>loadReact();</code>去加载ReactNative内容。</p>
<h2>Android打包</h2>
<p>  前面的操作完成后就基本完成了,现在需要打包一份离线JSBundle进去App,用于没网的时候App展示使用。</p>
<p>  首先在项目app/src/main下面必须要创建一个assets目录,否则后面生成会报<code>ENOENT: no such file or directory, open 'E:\Project\ExistedProject\app\src\main\assets\index.android.bundle'</code>。</p>
<p>  然后在项目目录下打开CMD,输入:</p>
<div class="highlight"><pre><span></span>react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --assets-dest app/src/main/res/
</pre></div>
<p>  看到<code>Done writing bundle output</code>就证明生成成功了。接下来直接运行App到手机上看看效果,成功看到离线内容。
<img alt="result" src="../images/rnIntergation/3.jpg"></p>
<h2>启动服务器</h2>
<p>  上面的查看效果是离线JSBundle的效果,如果你尝试reload的话就会发现出现error,因为连接不上服务器。直接在目录下输入:</p>
<div class="highlight"><pre><span></span>npm start
</pre></div>
<p>  启动服务器后不要关闭CMD,手机App注意设置Debug server host & port为主机的地址(具体可以参考我的<a href="http://blog.csdn.net/maxwell_nc/article/details/60960864">上一篇博文</a>),然后reload,看到App绿色的进度条或者服务器CMD中build的进度条就知道正在构建。成功后就可以看到服务器端的js效果。</p>
<p>  可以尝试修改提示语为“Hello ReactNative For Android!”并且保存,手机端reload既可以看到app端更新了的效果:
<img alt="update" src="../images/rnIntergation/4.jpg"></p>
<p>  注意你修改了资源可以不重新打包离线JSBundle进去,但是至少需要一份离线JSBundle才能运行App,否则Gradle编译无法通过。</p>
<h2>远程调试</h2>
<p>  如果你选在手机选择“Debug JS Remotely”,如果你没有安装chrome浏览器,不出意外就是npm服务器提示:
<img alt="update" src="../images/rnIntergation/5.jpg">
  然后手机端也红屏提示错误,实际上你只需要用任一款chrome壳浏览器打开<a href="http://localhost:8081/debugger-ui">http://localhost:8081/debugger-ui</a>然后重新reload,看到<code>Status: Debugger session #0 active.</code>就可以连上远程调试了,调试不是本文的内容就不再阐述了。</p>
<h2>尾声</h2>
<p>  ReactNative集成的过程中有很多的坑,如果你按照我的博文来操作可能你会说:“不会啊,一路流程非常轻松没问题啊”,但实际上本博文只是把可能的遇到问题和处理地方已经提前说明了。其中遇到的错误本文也列出来了,方便后面遇到的朋友也参考学习下。</p>
<h2>相关文章</h2>
<p><strong>Windows下搭建ReactNative开发环境(Android):</strong><a href="http://blog.csdn.net/maxwell_nc/article/details/60960864">http://blog.csdn.net/maxwell_nc/article/details/60960864</a></p>Android单元测试之JUnit框架2017-05-22T00:00:00+08:002017-05-22T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-22:/android/junitTest.html<h2>前序</h2>
<p>  最近抽空整理下项目,顺手整理了单元测试,于是记录下自己学习单元测试的心得,让更多的人快速用上单元测试提高开发效率。本文主要讲解如何使用JUnit框架进行单元测试,不会提及单元测试优缺点。</p>
<h2>在Android项目中使用JUnit</h2>
<p>  记得在Eclipse中集成Junit框架是一件很复杂的事情,首先要导入lib包,然后配置...现在如果使用Android Studio的话什么工作都不需要处理,创建一个新的项目就帮你配置好整个JUnit框架,你只需要专心写测试类即可。而且测试类也是可以自动生成的,如下图所示:
<img alt="dir" src="../images/junitTest/1.jpg">
  可以看到生成<code>app/src/main</code>为源码目录,对应<code>app/src/test</code>为测试类目录,Android Studio会自动生成一个ExampleUnitTest类,实际上这个test目录下的包名可以与src目录的不一样。</p>
<p>  以上图为例,我写了一个ExampleUtils作为本次测试例子,其代码为:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtils</span> <span class="o">{</span>
<span class="cm">/**</span>
<span class="cm"> * 获取完整的地址路径</span>
<span class="cm"> *</span>
<span class="cm"> * @param url 可能不完整的路径</span>
<span class="cm"> */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">getFullUrl</span><span class="o">(</span><span class="n">String</span> <span class="n">url</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">url</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"http …</span></pre></div><h2>前序</h2>
<p>  最近抽空整理下项目,顺手整理了单元测试,于是记录下自己学习单元测试的心得,让更多的人快速用上单元测试提高开发效率。本文主要讲解如何使用JUnit框架进行单元测试,不会提及单元测试优缺点。</p>
<h2>在Android项目中使用JUnit</h2>
<p>  记得在Eclipse中集成Junit框架是一件很复杂的事情,首先要导入lib包,然后配置...现在如果使用Android Studio的话什么工作都不需要处理,创建一个新的项目就帮你配置好整个JUnit框架,你只需要专心写测试类即可。而且测试类也是可以自动生成的,如下图所示:
<img alt="dir" src="../images/junitTest/1.jpg">
  可以看到生成<code>app/src/main</code>为源码目录,对应<code>app/src/test</code>为测试类目录,Android Studio会自动生成一个ExampleUnitTest类,实际上这个test目录下的包名可以与src目录的不一样。</p>
<p>  以上图为例,我写了一个ExampleUtils作为本次测试例子,其代码为:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleUtils</span> <span class="o">{</span>
<span class="cm">/**</span>
<span class="cm"> * 获取完整的地址路径</span>
<span class="cm"> *</span>
<span class="cm"> * @param url 可能不完整的路径</span>
<span class="cm"> */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">getFullUrl</span><span class="o">(</span><span class="n">String</span> <span class="n">url</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">url</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"http"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"http://"</span> <span class="o">+</span> <span class="n">url</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">url</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  如果你要创建一个对应的测试类,比较笨的方法就是到<code>app/src/test</code>目录下创建一个名字相近的ExampleUtilsTest类,然后一个个需要测试的方法都写一遍testXXX()的单元测试方法。</p>
<p>  强大的Android Studio可以帮我们完成这个操作,在任意需要测试的类(或者方法)下面按下Ctrl+Shift+T(这是默认热键,你也可以右键->Go To->Test)弹出如下提示:
<img alt="hint" src="../images/junitTest/2.jpg">
  如果你已经创建过,则会提示对应的测试类让你跳转过去,同样测试类也可以利用这个方法跳转到被测试类。根据上述操作创建一个新的测试类,然后会弹出提示界面:
<img alt="new" src="../images/junitTest/3.jpg">
  我们使用的库是JUnit4,这个无需修改,一般名字Class Name也无需修改,Generate这个后文再说,总之现在Member中勾选需要测试的方法。注意如果一个方法没有出现在这个列表上,证明这个方法无法测试。比如一个private声明的方法,他不需要测试也无法测试,因为它是属于类内部的过程,而单元测试不关系这个过程。</p>
<p>  选择方法后会提示你选择目标目录,可能为了兼容以前的版本还是怎么的,这里我们只需要选择第二个<code>app/src/test</code>目录,与Android Studio自动生成的目录相似即可:
<img alt="choose" src="../images/junitTest/4.jpg"></p>
<p>  可以看到生成的类,有些人可能喜欢改成testXXX,这个则需要自己手动修改了。点击每个方法前面的三角形就可以单独测试一个方法,点击类前面的两个三角形按钮则是一次运行类中所有的测试方法:
<img alt="sample" src="../images/junitTest/5.jpg">
  我们可以编写单元测试方法(Assert用法后文会说明):</p>
<div class="highlight"><pre><span></span> <span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">getFullUrl</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">testUrl</span> <span class="o">=</span> <span class="s">"https://maxwell-nc.github.io"</span><span class="o">;</span>
<span class="n">String</span> <span class="n">fullUrl</span> <span class="o">=</span> <span class="n">ExampleUtils</span><span class="o">.</span><span class="na">getFullUrl</span><span class="o">(</span><span class="n">testUrl</span><span class="o">);</span>
<span class="n">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="n">testUrl</span><span class="o">,</span> <span class="n">fullUrl</span><span class="o">);</span><span class="c1">//假设fullUrl与testUrl相同</span>
<span class="o">}</span>
</pre></div>
<p>  点击Run后则会生成一个测试结果,如果一条绿条就证明已经成功通过测试了,否则的话你可以查看哪一个方法存在问题,然后处理。
<img alt="pass" src="../images/junitTest/6.jpg"></p>
<h2>批量测试和生产报告</h2>
<p>  假设你只有一个类或者只有几个类需要测试的话,那可以直接使用上文说的方法来测试,但是假设你有很多的类和方法需要测试的话上面的操作就显得是十分笨拙。Android Studio的Gradle插件为我们生成了三个任务:</p>
<ul>
<li>testDebugUnitTest</li>
<li>testReleaseUnitTest</li>
<li>test</li>
</ul>
<p>  其中前两个任务是分别执行为Debug和Release模式下的所有单元测试,第三个任务就是执行前面两个任务。</p>
<p>  你可以在Terminal里面使用</p>
<div class="highlight"><pre><span></span>gradlew testDebugUnitTest
</pre></div>
<p>  来执行命令,由于是Wrapper可能需要额外的下载配置时间,也可以直接在面板中选择Task执行(使用本地的Gradle):
<img alt="task" src="../images/junitTest/7.jpg"></p>
<p>  等待执行完成就可以看到<code>build/reports/tests/</code>目录下对应的Html报告:
<img alt="build" src="../images/junitTest/8.jpg"></p>
<p>  使用浏览器打开可以看到详细测试报告:
<img alt="report" src="../images/junitTest/9.jpg"></p>
<h2>JUnit Assert</h2>
<p>  下面进入正题,上文我们使用了一个Assert.assertEquals方法来判断fullUrl和testUrl是否相同,其中这个Assert类就是用来验证结果的,有“假设”的意思。比如assertEquals方法就是“假设相同”的意思,如果不相同则会报错。</p>
<p>  那么除了assertEquals之外还有什么方法呢?我们可以从Assert源码结构观察出来,下面我们列举一下:</p>
<ul>
<li><strong>assertTrue</strong> 假设为真</li>
<li><strong>assertFalse</strong> 假设为假</li>
<li><strong>assertEquals</strong> 假设相同(基本数据类型或者对象)</li>
<li><strong>assertNotEquals</strong> 假设不相同(基本数据类型或者对象)</li>
<li><strong>assertNull</strong> 假设为空</li>
<li><strong>assertNotNull</strong> 假设不为空</li>
<li><strong>assertSame</strong> 假设相同(只能是对象)</li>
<li><strong>assertNotSame</strong> 假设不相同(只能是对象)</li>
<li><strong>assertArrayEquals</strong> 假设数组相同</li>
</ul>
<p>  可以看到源码中这些方法都有重载第一个参数多了String的方法。这个String是用于自定义错误信息,如果“假设”不符合预期,那么提示的错误信息使用这个String来指定。</p>
<p>  有时候计算机表示的数尤其是浮点型类型,可能两个值存在误差,设置一个可接受无法范围,也可以让假设通过。比如:</p>
<div class="highlight"><pre><span></span><span class="n">Assert</span><span class="o">.</span><span class="na">assertEquals</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mf">1.01</span><span class="o">,</span> <span class="mf">0.1</span><span class="o">);</span>
</pre></div>
<p>  上面表示预期值和实际值最大误差小于等于0.1即“假设”成立。另外注意这些方法的期望值都是前一个参数,实际值是后一个参数,不要写反了。</p>
<h2>JUnit Annotation</h2>
<p>  还记得上边创建测试类的时候出现了setUp和tearDown两个方法吗?分别对应@Before和@After这两个注解。实际上根据JUnit框架的设计,每个单元测试方法可以简单划分为:</p>
<ul>
<li><strong>setUp</strong> 对应 @Before注解的方法</li>
<li><strong>test</strong> 对应 @Test注解的方法</li>
<li><strong>tearDown</strong> 对应 @After注解的方法</li>
</ul>
<p>  如果创建时勾选这两个方法,则会生成:</p>
<div class="highlight"><pre><span></span><span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@After</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">tearDown</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="o">}</span>
</pre></div>
<p>  这两个方法会在当前类中<strong>每一个单元测试方法</strong>执行之前和执行之后分别执行。比如说需要创建一个实例,new instance()操作可以直接写在setUp方法中,减少冗余代码。同理假设要关闭一个文件流的话也可以写在tearDown方法中。</p>
<p>  注意看看@Test注解的注释,可以看到,它可以接受两个参数,一个是预期异常,一个是超时时间。</p>
<div class="highlight"><pre><span></span><span class="c1">//预期异常,不报错(如果不出现异常则报错)</span>
<span class="nd">@Test</span><span class="o">(</span><span class="n">expected</span><span class="o">=</span><span class="n">IndexOutOfBoundsException</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">outOfBounds</span><span class="o">()</span> <span class="o">{</span>
<span class="k">new</span> <span class="n">ArrayList</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">//超时报错</span>
<span class="nd">@Test</span><span class="o">(</span><span class="n">timeout</span><span class="o">=</span><span class="mi">100</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">infinity</span><span class="o">()</span> <span class="o">{</span>
<span class="k">while</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">//这种情况要小心,注意误差问题,有可能正确,有可能错误</span>
<span class="nd">@Test</span><span class="o">(</span><span class="n">timeout</span><span class="o">=</span><span class="mi">100</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sleep100</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">100</span><span class="o">);</span>
<span class="o">}</span>
</pre></div>
<p>  除了上述的注解之外,JUnit还提供其他很多方便的注解,我们可以通过查看JUnit的包看到比较常用的:</p>
<ul>
<li><strong>@BeforeClass</strong> 每一个测试类执行前的操作方法注解</li>
<li><strong>@AfterClass</strong> 每一个测试类执行完后的操作方法注解</li>
<li><strong>@Ignore</strong> 忽略某个测试方法注解(可以传入原因)</li>
</ul>
<p>  由于JUnit担心我们这些注解还不够用,所以给我们自定义规则的机会,于是有@Rule这个注解给我们去自定义规则。</p>
<h2>JUnit Rule</h2>
<p>  JUnit本身自带很多Rule,可以在org.junit.rules包中找到,例如比较简单的Timeout规则,可以直接创建一个类成员:</p>
<div class="highlight"><pre><span></span><span class="nd">@Rule</span>
<span class="kd">public</span> <span class="n">Timeout</span> <span class="n">timeout</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Timeout</span><span class="o">(</span><span class="mi">100</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">MILLISECONDS</span><span class="o">);</span>
</pre></div>
<p>  上述规则应用到类中所有测试方法,假设方法超过100毫秒则会报错,注意这个成员规则对象必须是Public的,还有一个比较常用的DisableOnDebug规则,可以设置在Run模式下使用的规则而Debug模式不使用的规则,如下代码:</p>
<div class="highlight"><pre><span></span><span class="nd">@Rule</span>
<span class="kd">public</span> <span class="n">DisableOnDebug</span> <span class="n">debug</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DisableOnDebug</span><span class="o">(</span><span class="k">new</span> <span class="n">Timeout</span><span class="o">(</span><span class="mi">100</span><span class="o">,</span> <span class="n">TimeUnit</span><span class="o">.</span><span class="na">MILLISECONDS</span><span class="o">));</span>
</pre></div>
<p>  上述代码表示Run模式测试则会限制100毫秒超时,而Debug模式不会限制。虽然JUnit给我们内置了很多自定义规则,但这显然是不够用的,所以需要我们去自定义规则,下面我们来说说如何自定义Junit Rule。</p>
<p>  在rules包下有一个TestRule接口用于给用户自定义Junit Rule,创建一个类去实现这个接口。然后重写apply方法。代码如下:</p>
<div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyTestRule</span> <span class="kd">implements</span> <span class="n">TestRule</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">Statement</span> <span class="nf">apply</span><span class="o">(</span><span class="n">Statement</span> <span class="n">base</span><span class="o">,</span> <span class="n">Description</span> <span class="n">description</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>  其中apply方法中的base参数代表单元测试的语句,他身上的方法:</p>
<div class="highlight"><pre><span></span><span class="n">base</span><span class="o">.</span><span class="na">evaluate</span><span class="o">();</span><span class="c1">//执行单元测试操作</span>
</pre></div>
<p>  而description则可以获取改单元测试方法的名称、注解、类名等等的描述。我们可以写一个简单的例子,如下:</p>
<div class="highlight"><pre><span></span><span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">Statement</span> <span class="nf">apply</span><span class="o">(</span><span class="kd">final</span> <span class="n">Statement</span> <span class="n">base</span><span class="o">,</span> <span class="n">Description</span> <span class="n">description</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">Statement</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">evaluate</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">Throwable</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"before"</span><span class="o">);</span><span class="c1">//测试前打印before</span>
<span class="n">base</span><span class="o">.</span><span class="na">evaluate</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="s">"after"</span><span class="o">);</span><span class="c1">//测试后打印after</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="o">}</span>
</pre></div>
<p>  然后在需要使用这个规则的测试类中声明这个规则:</p>
<div class="highlight"><pre><span></span><span class="nd">@Rule</span>
<span class="kd">public</span> <span class="n">MyTestRule</span> <span class="n">myTestRule</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MyTestRule</span><span class="o">();</span>
</pre></div>
<p>  运行测试方法即即可以看到输出:
<img alt="out" src="../images/junitTest/10.jpg">
  这样代表自定义的规则生效了。</p>
<h2>尾声</h2>
<p>  通过上文,我们学习了JUnit的用法,但是你会发现,光有JUnit框架并不能做完整的单元测试,比如说你想要使用<code>Log.i("tag","msg");</code>的时候,单元测试会失败并且提示:</p>
<blockquote>
<p>java.lang.RuntimeException: Method i in android.util.Log not mocked.</p>
</blockquote>
<p>  这是因为JUnit并不能在纯Java层面做测试,使用非纯Java API就会报错。这需要一些Mock框架来帮助我们进行测试,这个后面抽空会写一篇新的博文介绍。</p>MobSF Android静态分析使用心得2017-05-19T00:00:00+08:002017-05-19T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-19:/android/mobsfAnalys.html<h2>前序</h2>
<p>  此前也接触过一些第三方静态分析工具,因为工作原因接触了一款开源移动App安全框架<a href="https://github.com/MobSF/Mobile-Security-Framework-MobSF">MobileSecurityFramework</a>,经过一番折腾后决定写下一篇博文记录一下心得。</p>
<h2>搭建环境</h2>
<p>  本文是基于Windows搭建的Android App静态分析环境,首先是先要到<a href="https://github.com/MobSF/Mobile-Security-Framework-MobSF">Github</a>上拿去拉一份源码下来。由于这个框架是基于Python开发的,而且需要反编译Apk,所以我们列下需要的环境清单:</p>
<ul>
<li>Python 2.7(不能使3.x版本,低于2.7我也没有试过)</li>
<li>Oracle JDK 1.7+</li>
<li>MobSF源码</li>
</ul>
<p>  以上是静态分析需要的环境,另外再官方的文档中写了一句推荐使用虚拟机环境搭建,否则存在安全问题,这里只是试用一下就不使用虚拟机了。</p>
<p>  拿到源码后解压到一个目录里,在这个目录打开CMD,输入命令:</p>
<div class="highlight"><pre><span></span>py -2 pip install -r requirements.txt
</pre></div>
<p>  注意我这里因为同时安装了Python2.x和3.x,所以使用py启动器来指定版本,如果只有Python2.x,可以直接采用:</p>
<div class="highlight"><pre><span></span>pip install -r …</pre></div><h2>前序</h2>
<p>  此前也接触过一些第三方静态分析工具,因为工作原因接触了一款开源移动App安全框架<a href="https://github.com/MobSF/Mobile-Security-Framework-MobSF">MobileSecurityFramework</a>,经过一番折腾后决定写下一篇博文记录一下心得。</p>
<h2>搭建环境</h2>
<p>  本文是基于Windows搭建的Android App静态分析环境,首先是先要到<a href="https://github.com/MobSF/Mobile-Security-Framework-MobSF">Github</a>上拿去拉一份源码下来。由于这个框架是基于Python开发的,而且需要反编译Apk,所以我们列下需要的环境清单:</p>
<ul>
<li>Python 2.7(不能使3.x版本,低于2.7我也没有试过)</li>
<li>Oracle JDK 1.7+</li>
<li>MobSF源码</li>
</ul>
<p>  以上是静态分析需要的环境,另外再官方的文档中写了一句推荐使用虚拟机环境搭建,否则存在安全问题,这里只是试用一下就不使用虚拟机了。</p>
<p>  拿到源码后解压到一个目录里,在这个目录打开CMD,输入命令:</p>
<div class="highlight"><pre><span></span>py -2 pip install -r requirements.txt
</pre></div>
<p>  注意我这里因为同时安装了Python2.x和3.x,所以使用py启动器来指定版本,如果只有Python2.x,可以直接采用:</p>
<div class="highlight"><pre><span></span>pip install -r requirements.txt
</pre></div>
<p>  其中requirements.txt是运行MobSF的Python依赖环境。如果安装完成,接下来就是运行MobSF的服务器了,在命令行输入:</p>
<div class="highlight"><pre><span></span>python manage.py runserver
</pre></div>
<p>  同样需要注意Python版本问题,第一次启动服务器会自动安装服务器需要的东西,主要是nuget、binskim、binscope等东西,国内的用户注意代理,否则可能卡住不动。</p>
<p>  <strong>note:</strong>如果第一次安装失败不慎退出了,可以进入install目录先运行setup.py手动安装,然后在执行runserver命令。安装之后会自动生成一个自启动bat文件,根据源代码可以看出实际上他就是运行rpc_client.py。</p>
<p>  如果一切顺利的话,可以看到Django运行成功:
<img alt="Django" src="../images/mobsfAnalys/1.jpg">
  这个监听端口可以通过指定启动参数来修改,如:</p>
<div class="highlight"><pre><span></span>python manage.py runserver 8100
</pre></div>
<p>  然后可以打开浏览器输入地址,比如默认端口为:<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>,我这边出现了一个这样的问题(也许你可以直接看到成功画面,恭喜):
<img alt="error" src="../images/mobsfAnalys/2.jpg">
  如果和我一样出现“Don't Play Around. An Error just popped in!”的朋友,可以执行后重新启动服务器:</p>
<div class="highlight"><pre><span></span>python manage.py migrate
python manage.py makemigrations
</pre></div>
<p>  注意原因是“no such table: StaticAnalyzer_staticanalyzerandroid”才适用这个方法。成功之后就可以看到MobSF的界面:
<img alt="server" src="../images/mobsfAnalys/3.jpg">
  这样就算搭建完成了,当然也有可能上传App文件的时候发生错误,这就需要大家动动脑袋来处理了。</p>
<h2>MobSF静态分析</h2>
<p>  使用MobSF的静态分析十分简单,直接上传一个Apk包,等待服务器解包反编译分析结果即可。不过我使用了多次发现这个框架很有可能卡在MalwareAnalyzer上面,可能是联网检查的问题,具体我并没有分析,然后假设你中断了操作,下次再启动会重新解包重新分析,十分耗时。一切正常的话,你将会看到分析报告页面:
<img alt="Analys" src="../images/mobsfAnalys/4.jpg">
  这个分析报告可以说“仅供参考”,比如说PERMISSION的检测,含有<code>android.permission.INTERNET</code>就说Dangerous(后面会分析源码),这是比较令人费解的。毕竟这个权限只要是网络应用都会使用到,那岂不是所有的应用都是危险??</p>
<p>  而对于Code Analysis里面的ISSUE,其中一个“App can read/write to External Storage. Any App can read data written to External Storage.”也是SEVERITY为High,其实也只是提示你其他App可能会串改数据而已,而不是说你不能使用外部存储器,所以只要你访问了外部存储器的API就一定会报这个问题(汗颜)。</p>
<h2>源码及原理分析</h2>
<p>  由于上面给出的分析结果有点让人摸不着头脑,而且也没有标注错误的位置,所以只能从源码入手,分析其原理。源码的目录结构十分清晰,由于我们采用的事静态分析,可以直接找到<code>StaticAnalyzer</code>目录。</p>
<div class="highlight"><pre><span></span>├─migrations
├─test_files
├─tools
│ ├─apkid
│ │ └─rules
│ ├─d2j2
│ │ └─lib
│ ├─enjarify
│ │ ├─enjarify
│ │ │ ├─jvm
│ │ │ │ ├─constants
│ │ │ │ └─optimizatio
│ │ │ └─typeinference
│ │ └─tests
│ └─mac
└─views
├─android
└─ios
</pre></div>
<p>  通过打印StaticAnalyzer目录的树结构可以粗略知道,migrations是迁移文件,test_files是用来测试静态测试的文件,tools是用来反编译等的工具,views才是我们想要找的分析源码。
  直接到StaticAnalyzer\views\android目录下可以很快找到对应分析的源码(十分清晰的模块名)。比如我们找一下上文所述的Premission问题,一眼可以看到dvm_permissions.py,打开发现只是一个字典,对应每个权限和状态值、描述等信息:</p>
<div class="highlight"><pre><span></span> <span class="s2">"INTERNET"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"dangerous"</span><span class="p">,</span> <span class="s2">"full Internet access"</span><span class="p">,</span> <span class="s2">"Allows an application to create network sockets."</span><span class="p">]</span>
</pre></div>
<p>  这还不能说明什么,我们可以继续发现manifest_analysis.py文件中导入了dvm_permissions,其中代码中:</p>
<div class="highlight"><pre><span></span><span class="n">permissions</span> <span class="o">=</span> <span class="n">mfxml</span><span class="o">.</span><span class="n">getElementsByTagName</span><span class="p">(</span><span class="s2">"uses-permission"</span><span class="p">)</span>
<span class="o">...</span>
<span class="k">for</span> <span class="n">permission</span> <span class="ow">in</span> <span class="n">permissions</span><span class="p">:</span>
<span class="n">perm</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">permission</span><span class="o">.</span><span class="n">getAttribute</span><span class="p">(</span><span class="s2">"android:name"</span><span class="p">))</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">perm</span><span class="p">:</span>
<span class="n">prm</span> <span class="o">=</span> <span class="n">i</span>
<span class="n">pos</span> <span class="o">=</span> <span class="n">i</span><span class="o">.</span><span class="n">rfind</span><span class="p">(</span><span class="s2">"."</span><span class="p">)</span>
<span class="k">if</span> <span class="n">pos</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
<span class="n">prm</span> <span class="o">=</span> <span class="n">i</span><span class="p">[</span><span class="n">pos</span> <span class="o">+</span> <span class="mi">1</span><span class="p">:]</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">dvm_perm</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">DVM_PERMISSIONS</span><span class="p">[</span><span class="s2">"MANIFEST_PERMISSION"</span><span class="p">][</span><span class="n">prm</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="n">dvm_perm</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">"dangerous"</span><span class="p">,</span>
<span class="s2">"Unknown permission from android reference"</span><span class="p">,</span>
<span class="s2">"Unknown permission from android reference"</span>
<span class="p">]</span>
</pre></div>
<p>  看以看出这个权限的检测直接就是根据dvm_permissions.py中定义的字典来决定的,并没有更多的判断规则。</p>
<p>  接下来我们看看Code Analysis ISSUE:“The App uses an insecure Random Number Generator.”的判断原理。同理我们也可以找到code_analysis.py文件直接分析。先找到一个字典字段描述这个问题:</p>
<div class="highlight"><pre><span></span><span class="s1">'rand'</span><span class="p">:(</span><span class="s1">'The App uses an insecure Random Number Generator.'</span><span class="p">),</span>
</pre></div>
<p>然后我们搜索key'rand'可以看到,</p>
<div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">typ</span> <span class="o">==</span> <span class="s2">"apk"</span><span class="p">:</span>
<span class="n">java_src</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">app_dir</span><span class="p">,</span> <span class="s1">'java_source/'</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">typ</span> <span class="o">==</span> <span class="s2">"studio"</span><span class="p">:</span>
<span class="n">java_src</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">app_dir</span><span class="p">,</span> <span class="s1">'app/src/main/java/'</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">typ</span> <span class="o">==</span> <span class="s2">"eclipse"</span><span class="p">:</span>
<span class="n">java_src</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">app_dir</span><span class="p">,</span> <span class="s1">'src/'</span><span class="p">)</span>
<span class="o">...</span>
<span class="n">dat</span> <span class="o">=</span> <span class="n">file_pointer</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="o">...</span>
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="sa">r</span><span class="s1">'java\.util\.Random'</span><span class="p">,</span> <span class="n">dat</span><span class="p">):</span>
<span class="n">code</span><span class="p">[</span><span class="s1">'rand'</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">jfile_path</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">java_src</span><span class="p">,</span><span class="s1">''</span><span class="p">))</span>
</pre></div>
<p>  表示只要使用了<code>java.util.Random</code>这个类就会报这个问题。实际上即使是<code>java.security.SecureRandom</code>也存在安全风险。所以这类问题还是比较难处理的,不过如果不涉及安全的随机数(比如为用户起一个随机昵称,而这个昵称并不作为唯一标识),即使使用了也没有关系,这里就不展开讨论了。</p>
<p>  其他还有很多检查大体原理相似,如果经常需要用到某一个检测,也可以把那部分源码单独拷出来做成一个单独检测工具,这样不需要每次都去完整的检测才知道结果。</p>
<h2>尾声</h2>
<p>  使用过后我觉得很失望,很多功能都没有,比如:</p>
<ul>
<li>不支持排除第三方</li>
<li>不支持显示错误行数或者位置</li>
<li>不支持Mapping</li>
<li>不支持自定义规则</li>
<li>不支持标记已处理的问题</li>
</ul>
<p>  很多情况下使用这个框架,都是由程序员搭建好一个服务器供开发人员或者是非开发人员去检测使用。倘若出了这么一份充满Dangerous和High SEVERITY的报告给非技术人员看,更重要的是无论你怎么改都无法去掉,这想必得花好一段时间去解析吧?所以个人不是很推荐这个框架给非技术人员使用。</p>
<p>  当然目前这个框架还处于Beta阶段,版本号也没有到1.0,我仅仅使用了它的静态分析功能,它还有动态分析等等,总体来说这是一个很不错的工具,但是还没有足够的完善,我们期待他更好地发展。特别是规则自定义,希望可以单独出来,这样可以让更多开源力量去维护增强它。</p>Windows下使用Pelican搭建静态博客2017-05-17T00:00:00+08:002017-05-17T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-17:/blog/pelicanBuildBlog.html<h2>前序</h2>
<p>  首先Pelican的知名度在国内明显没有Hexo的高,导致也没有几篇教程。遂写一篇简单的Windows搭建教程,给小白们快速使用Pelican搭建静态博客并且发表到Gtihub Pages上。</p>
<h2>安装Pelican</h2>
<p>  在Windows下搭建pelican环境可以说完爆Jekyll,如果熟悉Python,甚至比Hexo还要简单,闲话不多说,直接开干。</p>
<p>  因为Pelican是基于Python开发的,所以首先得安装Python环境。我目前使用的是<a href="https://www.python.org/downloads/">Python 3.6.1</a>,这个版本再Windows安装的话连环境都不需要手动配置即可完成。</p>
<p>  接下来需要使用Python包管理工具,建议没有安装pip的可以使用easy_install安装:</p>
<div class="highlight"><pre><span></span>easy_install pip
</pre></div>
<p>  安装完成后使用pip安装pelican和markdown</p>
<div class="highlight"><pre><span></span>pip install markdown
pip install pelican
</pre></div>
<p>  如果一切正常的话,恭喜已经搭建完成Pelican环境了,是不是很简单?</p>
<h2>创建博客</h2>
<p>  首先你需要一个存放你的博客的目录,进入到目录里面,打开cmd,输入下面命令可以生成一个基本的博客模板:</p>
<div class="highlight"><pre><span></span>pelican-quickstart
</pre></div>
<p>  如下图所示会询问你一些设置,这些设置会生成对应的pelicanconf.py配置文件和Makefile文件,不过Windows下由于不能使用Makefile,所以这里即使填错选项也可以待会在修改。</p>
<p><img alt="pelican-quickstart" src="../images/pelicanBuildBlog/1.jpg"></p>
<p>  如果提示"Done. Your new project …</p><h2>前序</h2>
<p>  首先Pelican的知名度在国内明显没有Hexo的高,导致也没有几篇教程。遂写一篇简单的Windows搭建教程,给小白们快速使用Pelican搭建静态博客并且发表到Gtihub Pages上。</p>
<h2>安装Pelican</h2>
<p>  在Windows下搭建pelican环境可以说完爆Jekyll,如果熟悉Python,甚至比Hexo还要简单,闲话不多说,直接开干。</p>
<p>  因为Pelican是基于Python开发的,所以首先得安装Python环境。我目前使用的是<a href="https://www.python.org/downloads/">Python 3.6.1</a>,这个版本再Windows安装的话连环境都不需要手动配置即可完成。</p>
<p>  接下来需要使用Python包管理工具,建议没有安装pip的可以使用easy_install安装:</p>
<div class="highlight"><pre><span></span>easy_install pip
</pre></div>
<p>  安装完成后使用pip安装pelican和markdown</p>
<div class="highlight"><pre><span></span>pip install markdown
pip install pelican
</pre></div>
<p>  如果一切正常的话,恭喜已经搭建完成Pelican环境了,是不是很简单?</p>
<h2>创建博客</h2>
<p>  首先你需要一个存放你的博客的目录,进入到目录里面,打开cmd,输入下面命令可以生成一个基本的博客模板:</p>
<div class="highlight"><pre><span></span>pelican-quickstart
</pre></div>
<p>  如下图所示会询问你一些设置,这些设置会生成对应的pelicanconf.py配置文件和Makefile文件,不过Windows下由于不能使用Makefile,所以这里即使填错选项也可以待会在修改。</p>
<p><img alt="pelican-quickstart" src="../images/pelicanBuildBlog/1.jpg"></p>
<p>  如果提示"Done. Your new project is available at xxx path"这样就是已经生成成功。</p>
<p>  下面我们来观察下生成的文件:
<img alt="files" src="../images/pelicanBuildBlog/2.jpg">
  注意的是publishconf.py完全导入pelicanconf.py文件,所以前者用于本地调试使用,后者发布时自动替换某些属性,例如SITEURL,不过由于我们不使用Makefile,所以直接使用pelicanconf.py文件即可。</p>
<p>  fabfile.py里面包括各种Deploy相关的配置和操作,可以在里面修改默认的本地服务器端口,一般来说都不需要修改。下面我们来写第一篇博客试试。</p>
<h2>编写文章</h2>
<p>  我们首先写一篇HelloPelican文章然后生成发布到本地瞧瞧是怎么个样子。上文说过content目录是用来存放博文等文件目录的,直接在目录里面新建一个文件test.md(本文使用MarkDown来编辑,pelican也支持reStructuredText)。</p>
<div class="highlight"><pre><span></span>Title: HelloPelican
Date: 2017-05-17
Category: test
Tags: test
Slug: blog/hello
Author: Maxwell-nc
<span class="gu">##</span>First
Text
</pre></div>
<p>  其中Title等字段用来声明博客的属性,Slug为对应生成html的相对路径,如果上述最终生成路径是blog目录下的hello.html,这些字段还可以自定义,这个进阶内容可以后面再说。出去属性声明外的部分都是正文内容,##First就是正文的开始了。</p>
<p>  简单编辑后就可以尝试生成html,并且在本地预览了。生成html可以使用下面的命令:</p>
<div class="highlight"><pre><span></span>pelican content
</pre></div>
<p>  生成成功后,使用下面命令启动本地服务器:</p>
<div class="highlight"><pre><span></span>python -m pelican.server
</pre></div>
<p>  如果需要临时指定非默认端口也可以在命令后面添加端口参数,如设置8080端口:</p>
<div class="highlight"><pre><span></span>python -m pelican.server 8080
</pre></div>
<p>  如果服务器启动成功,就可以通过浏览器访问<a href="http://127.0.0.1:8000">http://127.0.0.1:8000</a>来预览下效果:
<img alt="preview" src="../images/pelicanBuildBlog/3.jpg">
  可以看到已经生成博客成功,这样就已经完成了一大步了。</p>
<h2>静态文件</h2>
<p>  通过上文,我们已经成功添加第一篇博客,但是很快会发现,如果你往content目录里面添加一个images文件夹存放博文的图片,你会发现<code>pelican content</code>并不会复制images文件夹到output目录下。这种不需要编译但又要用到的文件,我们称它为“静态文件”。pelican默认不会复制静态文件到output目录,需要我们在pelicanconf.py配置文件上面配置一下,添加一行:</p>
<div class="highlight"><pre><span></span><span class="n">STATIC_PATHS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'images'</span><span class="p">]</span>
</pre></div>
<p>  这样就会生成output资源时就会自动把iamges文件夹拷贝到output目录了。另外使用EXTRA_PATH_METADATA也可以把某个目录的文件映射过去,例如favicon.ico放在content/extra目录下,最后需要生成到output的根目录,可以添加:</p>
<div class="highlight"><pre><span></span><span class="n">STATIC_PATHS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'images'</span><span class="p">,</span> <span class="s1">'extra/favicon.ico'</span><span class="p">]</span>
<span class="n">EXTRA_PATH_METADATA</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'extra/favicon.ico'</span><span class="p">:</span> <span class="p">{</span><span class="s1">'path'</span><span class="p">:</span> <span class="s1">'favicon.ico'</span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2>自动生成发布脚本</h2>
<p>  由于Windows不能使用makefile,不能使用<code>make html</code>这样快捷的方法来生成Html,所以我们利用Windows的批处理做一个简单的"Makefile"。</p>
<p>  在博客根目录下新建一个auto-update.bat批处理文件,修改内容为:</p>
<div class="highlight"><pre><span></span><span class="p">@</span><span class="k">echo</span> off
<span class="k">setlocal</span> enabledelayedexpansion
<span class="k">for</span> <span class="k">/f</span> <span class="s2">"delims= tokens=1"</span> <span class="se">%%</span>i <span class="k">in</span> <span class="p">(</span><span class="s1">'netstat -aon ^| findstr "8000"'</span><span class="p">)</span> <span class="k">do</span> <span class="p">(</span>
<span class="k">set</span> <span class="nv">a</span><span class="p">=</span><span class="se">%%</span>i
<span class="k">goto</span> <span class="nl">job</span>
<span class="p">)</span>
<span class="p">:</span><span class="nl">job</span>
taskkill /F /pid <span class="s2">"</span><span class="nv">!a:~71,5!</span><span class="s2">"</span>
pelican content
<span class="k">cd</span> output
<span class="k">start</span> cmd /c <span class="s2">"python -m pelican.server 8000"</span>
<span class="k">cd</span> ..
</pre></div>
<p>  上面的代码内容是找到已经运行的服务器程序,结束它,然后重新生成Output文件,并且重新打开本地服务器。如果要修改端口可以替换bat文件中的端口号。有了这个脚本,以后需要更新预览,只需要在博客目录下打开CMD,输入auto-update.bat,敲一下回车就能自动完成了。</p>
<p>  <strong>注意:</strong>在pelicanconf.py添加<code>DELETE_OUTPUT_DIRECTORY = True</code>可以每次生成html前都删除Output目录的文件,防止一些缓存导致的问题。</p>
<h2>发布到Github Pages</h2>
<p>  很简单,把Github.io项目拉下来,用Output目录里面的内容替换掉,push上去刷新就能看到了。不过这里需要注意的是是否配置了<code>RELATIVE_URLS</code>这个相对路径设置,<code>SITEURL</code>也要设置成Pages的地址,否者Feed的xml地址将显示不完全,编译的时候也会提示:
"WARNING: Feeds generated without SITEURL set properly may not be valid"。所以这些都要手动检查清楚后再发布。</p>
<h2>第三方主题</h2>
<p>  如果没有什么特别的需求的话,教程就到这里结束了,但是对于官方简陋的主题很多人都希望换一个主题。如果你使用过Hexo的主题甚至修改过,那恭喜你,这一步对于你来说十分简单。</p>
<p>  首先我们可以在<a href="https://github.com/getpelican/pelican-themes">https://github.com/getpelican/pelican-themes</a>找一个合心意的主题,然后拉下来,解压到博客目录下的Theme目录(这个目录是自己建立的,你可以起的别名字),在pelicanconf.py下增加一行:</p>
<div class="highlight"><pre><span></span><span class="n">THEME</span> <span class="o">=</span> <span class="s1">'Theme/nest'</span>
</pre></div>
<p>  其中这个nest是你的主题的目录名,本博客采用的是基于nest修改的主题,可以在<a href="https://github.com/maxwell-nc/nest">https://github.com/maxwell-nc/nest</a>找到源代码,感谢它的作者。言归正传,添加了这行之后理论上就可以重新生成发布就可以看到了,但是要注意有些主题需要额外的参数,具体看每个主题页面的ReadMe,这里就不一样介绍了。</p>
<h2>修改主题</h2>
<p>  下面我们来简单进阶一下,尝试一下修改主题(注意这需要一点点Html知识)。下面以在博文最后添加转载信息为例。</p>
<p>  首先我们打算在每篇文章的头部添加一个自定义字段Reprint,这个字段代表转载地址字段,如果这个字段有值,则显示“转载文章”,否则显示“原创文章”。我们进入主题目录中的templates目录,templates代表模板的意思,其中article.html就是每篇文章的生成的模板。html中<code>article.content</code>是正文的内容,我们一直定位到article.content位置,在下面添加,</p>
<div class="highlight"><pre><span></span>{{ article.content }}
<span class="p"><</span><span class="nt">br</span><span class="p">/><</span><span class="nt">br</span><span class="p">/></span>
<span class="p"><</span><span class="nt">p</span> <span class="na">style</span><span class="o">=</span><span class="s">"color: #eb2344;"</span><span class="p">><</span><span class="nt">b</span><span class="p">></span>
{% if article.reprint %}
本文为转载文章,原文链接:<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ article.reprint }}"</span> <span class="na">target</span><span class="o">=</span><span class="s">"_blank"</span><span class="p">></span>{{ article.reprint }}<span class="p"></</span><span class="nt">a</span><span class="p">></span>
{% else %}
原创文章,欢迎转载,请保留出处。有任何错误、疑问或者建议,欢迎指出。
{% endif %}
<span class="p"></</span><span class="nt">b</span><span class="p">></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">br</span><span class="p">/><</span><span class="nt">br</span><span class="p">/></span>
</pre></div>
<p>  新增的字段可以用article.reprint来访问,支持使用<code>{% if article.reprint %}</code>结合<code>{% else %}</code>和<code>{% endif %}</code>的组合来实现判断。
接下来可以在一篇文章的头部,如果是转载文章,可以添加Reprint属性来声明转载文章,实现自动显示转载信息:</p>
<div class="highlight"><pre><span></span>Title: xxx
Date: 2017-05-17
Category: xxx
Tags: xxx
Slug: blog/xxx
Author: Maxwell-nc
Reprint: https://github.com/maxwell-nc
</pre></div>
<p>  主题的修改除了添加转载信息之外还以修改主题的布局等等功能,除了if条件表达式还有for表达式等等的功能,或者添加评论插件、音乐播放器等等,更多的功能可以参考官方文档,这里就不重复了。</p>
<h2>尾声</h2>
<p>  Pelican入门的成本非常低,如果只要普通做个静态博客,相信只要一杯咖啡的时间,但是Pelican的功能远不止上述这些,本文只是抛砖引玉,感兴趣的朋友可以看看参考文章中的官方文档。</p>
<h2>参考文章</h2>
<p>  Pelican官方文档:<a href="http://docs.getpelican.com/en/3.7.1/index.html">http://docs.getpelican.com/en/3.7.1/index.html</a></p>博客说明2017-05-16T00:00:00+08:002017-05-16T00:00:00+08:00Maxwell-nctag:maxwell-nc.github.io,2017-05-16:/blog/blogIntroduction.html<h2>前序</h2>
<p>  由于之前用Hexo搭建的GitPages源目录丢失了,本来已经打算Github上面维护的博客停止维护了,但是<a href="http://blog.csdn.net/maxwell_nc">我的CSDN博客</a>强制要求用户绑定隐私信息,否则无法登陆,而且此前没有任何公开提前声明或通知,于是决定放弃CSDN的博客,继续拥抱GithubPages。</p>
<p>  然后最近发现了Python也有Pelican这样类似的静态博客搭建工具,由于自己对Python比较熟悉,于是重新用Pelican搭建了一个博客。</p>
<p>  虽然没有找到Windows下的Pelican教程,但是参考Linux平台下的教程也是非常轻松搭建起来了,之后有空的话顺便写一个教程(PS:由于Windows不能使用Makefile,我写了个简单的批处理代替它)。</p>
<h2>关于博文</h2>
<p>  不得不说这个问题,由于上文说到我的CSDN博客无法登陆,所以无法迁移上面的博文,需要看的朋友只能移步过去了,在博客下面有一个Link可以快速跳转过去哦。</p>
<p>  而原来Hexo搭建的Pages文章由于数量比较少,博文主要是项目说明,所以把原来比较重要的博文内容迁移到对应项目的ReadMe上面,这样就不会有太大损失了(苦,手动迁移)。</p>
<p>  以后会坚持带更高质量的文章给大家,有兴趣的看看原来的Hexo博客也可以查看下History拉到本地跑起来看看。</p>
<h2>关于主题</h2>
<p>  看了下Pelican的主题并没有Hexo的主题多,虽然说可以把它移植过来,但是出于时间考虑,我只是找一个个人认为比较美观简洁的主题,然后最后还是花了半天修改了下样式和一些设置。</p>
<p>  这个修改后的主题可以在<a href="https://github.com/maxwell-nc/nest">这里</a>找到,非常感谢原作者。</p>
<h2>关于评论</h2>
<p>  综合各种原因,采用了Disqus第三方评论系统,这个评论系统在国外十分流行,可惜国内无法访问,本来想采用国内的平台,无奈不是已经关闭就是需要登记隐私信息 …</p><h2>前序</h2>
<p>  由于之前用Hexo搭建的GitPages源目录丢失了,本来已经打算Github上面维护的博客停止维护了,但是<a href="http://blog.csdn.net/maxwell_nc">我的CSDN博客</a>强制要求用户绑定隐私信息,否则无法登陆,而且此前没有任何公开提前声明或通知,于是决定放弃CSDN的博客,继续拥抱GithubPages。</p>
<p>  然后最近发现了Python也有Pelican这样类似的静态博客搭建工具,由于自己对Python比较熟悉,于是重新用Pelican搭建了一个博客。</p>
<p>  虽然没有找到Windows下的Pelican教程,但是参考Linux平台下的教程也是非常轻松搭建起来了,之后有空的话顺便写一个教程(PS:由于Windows不能使用Makefile,我写了个简单的批处理代替它)。</p>
<h2>关于博文</h2>
<p>  不得不说这个问题,由于上文说到我的CSDN博客无法登陆,所以无法迁移上面的博文,需要看的朋友只能移步过去了,在博客下面有一个Link可以快速跳转过去哦。</p>
<p>  而原来Hexo搭建的Pages文章由于数量比较少,博文主要是项目说明,所以把原来比较重要的博文内容迁移到对应项目的ReadMe上面,这样就不会有太大损失了(苦,手动迁移)。</p>
<p>  以后会坚持带更高质量的文章给大家,有兴趣的看看原来的Hexo博客也可以查看下History拉到本地跑起来看看。</p>
<h2>关于主题</h2>
<p>  看了下Pelican的主题并没有Hexo的主题多,虽然说可以把它移植过来,但是出于时间考虑,我只是找一个个人认为比较美观简洁的主题,然后最后还是花了半天修改了下样式和一些设置。</p>
<p>  这个修改后的主题可以在<a href="https://github.com/maxwell-nc/nest">这里</a>找到,非常感谢原作者。</p>
<h2>关于评论</h2>
<p>  综合各种原因,采用了Disqus第三方评论系统,这个评论系统在国外十分流行,可惜国内无法访问,本来想采用国内的平台,无奈不是已经关闭就是需要登记隐私信息,所以最终还是采用了Disqus。</p>
<p>  这个问题让我折腾前后试了好几款评论系统,本来觉得评论是最快的交流方式,不过由于博客在Pages上,国内的搜索引擎并不收录,所以国内的朋友请使用下面的联系方式和我交流↓↓↓。</p>
<h2>关于联系方式</h2>
<p>  虽然Otulook邮箱可以使用,但是我很少查看,加上上次微软回收了一次这个账号,所以如果不是发不到163邮箱,尽量发到163邮箱,很高兴能和你交流讨论学习。</p>
<p>163邮箱:
  <a href="mailto:maxwell_nc@163.com">maxwell_nc@163.com</a></p>
<p>Outlook邮箱:
  <a href="mailto:maxwell-nc@outlook.com">maxwell-nc@outlook.com</a></p>