關于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對象里的方法。
這一篇文章的內容相對比較抽象和晦澀,我可能未必講清楚了,只是梳理了一下自己的思路,歡迎大家和我交流,共同成長。