关于Rxjava的第二篇文章来了,前几天由于我情绪不佳暂时中断几天。今天终于恢复心情,开始继续自己梳理工作。
在之前的文章《Rxjava梳理(1)--Observable,Observer,subscribe》里讲到了Observable,observer,subscribe这些Rxjava中的基本概念。本篇文章想梳理一下Rxjava里面最有用以及最难以理解的一个特性:"变换(Transform)"。有人会觉得Rxjava很好用,又有些人会觉得Rxjava很难理解,绝大多数正是因为Rxjava的变换特性。
用例子来说明吧:假设有一个方法是要把一个字符串显示在文本控件上,一般方法如下
public void displayText(String text){
textView.setText(text);
}
而如果运用上一篇文章所讲的,就应该这样写
public void displayText(String text){
Observable.just(text).subscribe( new Action1<String>(){
public void call(String s){
textView.setText(s);
}
});
}
这个时候如果有这样的一个需求,需要对该字符串在显示之前,先做一些处理转换成新的字符串再打印,应该如何做呢?这时候我们可以这样做
public void displayText(String text){
String newText = convertToNewText(text);
Observable.just(newText).subscribe( new Action1<String>(){
public void call(String s){
textView.setText(s);
}
});
}
这样做有一个问题就是如果我们是从第三方库里得到的是封装了该字符串对象的Observable对象应该如何做呢?当然这样,我们也可以这样做
public void displayText(String text){
Observable.just(text).subscribe( new Action1<String>(){
public void call(String s){
String newText = convertToNewText(s);
textView.setText(newText);
}
});
}
这种方法也有一个问题,就是 call方法里的方法明显应该放在主线程里,如果convertToNewText()方法是一个非常耗时的方法的话就会对应用程序的性能造成很大的影响,所以尽量保证call方法里的处理逻辑简洁是很重要的事情,这就使得我们可以尝试map()方法来尝试解决这个问题
public void displayText(String text){
Observable.just(text).map(new Func1<String,String>(){
public String call(String text){
return convertToNewText(text);
}
}).subscribe( new Action1<String>(){
public void call(String s){
textView.setText(s);
}
});
}
通过这个例子可以看出来,map方法通过Func1这个对象把一个字符串转换为另一个字符串,并在订阅的方法里显示出这个改变了的字符串,和之前的方法比,是不是逻辑更加清晰些了呢?当然,Func1对象不仅仅可以转换成相同类型的对象,还可以把一种类型的对象转换成其他类型的对象,比如说上面的例子,如果我们想显示该字符串的长度呢?
public void displayLengthOfText(String text){
Observable.just(text).map(new Func1<String,Integer>(){
public Integer call(String text){
return text.length();
}
}).map(new Func1<Integer,String>(){
public String call(int length){
return Integer.toString(length);
}
}).subscribe(new Action1<Integer>(){
public void call(String length){
textView.setText(length);
}
})
}
可见这种方式把每一种转换都单独放到了一个方法里,让逻辑更加清晰。
如果情况更加复杂了呢,比如,我得到一个字母列表比如{"a","b","c"}。这个列表里面的元素又需要到单词表里搜出以该字母为首字母的单词列表,比如以a为首字母的列表就会有{"cat","career","chat"...},而我订阅的方法就需要把这些单词打印出来,这样一个需求如何实现呢?我们可以这样来做
public void displayWord(List<String> letters){
Observable.from(letters).map(new Func1<String,List<String>>(){
public List<String> call(String letter){
List<String> words = getWordsByLetter(letter);
return words
}
}).subscribe(new Action1<List<String>>(){
public void call(List<String> words){
for(String word:words){
System.out.println(word);
}
}
});
}
这种实现的问题就会把过多的代码都放到了Action1类的方法里,不利于对每一个字符串对象进行变换处理。这时候flatMap()方法被提了出来
public void displayWord(List<String> letters){
Observable.from(letters).flatMap(new Func1<String,Observable<String>>(){
public Observable<String> call(String letter){
Observable<String> words= Observable.from(getWordsByLetter(letter));
return words;
}
} ).subscribe(new Action1<String>(){
public void call(String word){
System.out.println(word);
}
});
}
从上面的例子可以看到,flatMap里的Func1对象里的call()方法返回的是Observable对象.flatMap()方法的原理如下:
- flatMap()方法返回一个大的Observable对象
- call()方法并不是返回Observable对象给步骤1的大Observable对象,而是把它的事件(即String对象)传给了大的Observable对象
- 这样大的Observable对象汇聚了所有的匿名类中生成的Observable对象里的事件,统一发给了Subscriber对象。
要了解这些转换的原理的话,就需要了解lift(Operator)方法,该方法的一些重要的代码如下:
public<R> Observable<R> lift(Operator<? extends R,? super T> oprator){
return Observable.create(new OnSubscibe<R>(){
public void call(Subscriber subscriber){
Subscriber newSubscriber = operator.call(subscriber);
newSubscriber.onStart();
onSubscribe.call(newSubscriber);
}
});
}
需要解释一下lift() 方法的原理:
- lift()方法生成了一个新的Observable对象,和原来的Observable对象并存。
- 当lift()方法生成的新的Observable对象调用subscribe()方法的时候,该Obsersable的OnSubscriber对象的call()方法被调用,就是上面代码块中的方法。
- call方法里首先用了operator.call()方法用我们调用的Subscriber对象生成了一个新的Subscriber对象,并且把两者连接起来
- 后面的onSubscribe方法是原始的Observable对象里面的OnSubscribe对象,通过call方法调用了新的Subscriber对象里的方法,实际上也就间接调用了原来的Subscriber对象里的方法。
这一篇文章的内容相对比较抽象和晦涩,我可能未必讲清楚了,只是梳理了一下自己的思路,欢迎大家和我交流,共同成长。