又是新的一天,若是學點新東西,這一天必定會很酷炫。javascript
小夥伴們一切順利啊,這是咱們的 RxJava2 Android 系列的第四部分 [ 第一部分, 第二部分, 第三部分 ]。 好消息是咱們已經作好準備,能夠開始使用 Rx 了。在使用 RxJava2 Android Observable 以前,我會先用 Java8 的 Stream 來作響應式編程。我認爲咱們應該瞭解 Java8,並且經過使用 Java8 的 Stream API 讓我感受學習 RxJava2 Android 的過程更簡單。
動機:前端
動機跟我在 第一部分 和你們分享過的同樣。在我開始學習 RxJava2 Android 的時候,我並不知道本身會在什麼地方,以何種方式使用到它。java
如今咱們已經學會了一些預備知識,但當時我什麼都不懂。所以我開始學習如何根據數據或對象建立 Observable 。而後知道了當 Observable 的數據發生變化時,應該調用哪些接口(或者能夠叫作「回調」)。這在理論上很好,可是當我付諸實踐的時候,卻 GG 了。我發現不少理論上應該成立的模式在我去用的時候徹底不起做用。對我來講最大的問題,是不能用響應或者函數式響應的思惟思考問題。我熟悉命令式編程和麪向對象編程,因爲先入爲主,因此對我來講理解響應式會有些難。我一直在問這些問題:我該在哪裏實現?我應該怎麼實現?若是你能堅持看完這篇文章,我能夠 100% 保證你會知道怎樣把命令式代碼轉換成 Rx 代碼,雖然寫出來的 Rx 代碼不是最好的,但至少你知道該從哪裏入手了。react
回顧:android
我想回顧以前三篇文章中咱們提到過的全部概念 [ 第一部分、第二部分、 第三部分 ]。由於如今咱們要用到這些概念了。在 第一部分 咱們學習了觀察者模式; 在 第二部分 學習了拉模式和推模式、命令式和響應式;在 第三部分 咱們學習了函數式接口(Functional Interfaces)、 接口默認方法(Default Methods)、高階函數(Higher Order Functions)、函數的反作用(Side Effects in Functions)、純函數(Pure Functions)、Lambda 表達式和函數式編程。我在下面寫了一些定義(很無聊的東西)。若是你清楚這些定義,能夠跳到下一部分。
函數式接口是隻有一個抽象方法的接口。
在 Java8 咱們能夠在接口中定義方法,這種方法叫作「默認方法」。
至少有一個參數是函數的函數和返回類型爲函數的函數稱爲高階函數。
純函數的返回值僅僅由參數決定,不會產生可見的反作用(好比修改一些影響程序狀態的值。——譯註)。
Lambda 表達式在計算機編程中又叫作匿名函數,是一種在聲明和執行的時候不會跟標識符綁定的函數或者子程序。ios
簡介:git
今天咱們將向 RxJava 的學習宣戰。我肯定在最後咱們會取得勝利。github
做戰策略:面試
Java8 Stream(這使得咱們快速開始,咱們將從 Android 開發者的角度來看)express
Java8 Stream 向 Rx Observable 轉變
RxJava2 Android 示例
技巧,怎樣把命令式代碼轉爲 RxJava2 Android 代碼
是時候根據咱們的策略發動進攻了,兄弟們上。
1. Java8 Stream:
如今我用 IntelliJ 這個 IDE 來寫 Java8 的 Stream。你可能會想爲何我去使用在 Android 不支持的 Java8 的 Stream。對於這樣想的同志,我來解釋一下。主要有兩個緣由。首先,我知道幾年後 Java8 將成爲 Android 開發的一等公民。因此你應該瞭解關於 Stream 的 API,而且在面試中你可能被問到。並且,Java8 的 Stream 和 Rx Observable 在概念上很像。因此,爲何不一次性把這兩個東西一塊兒學了呢?其次,我感受不少像我同樣能力低下、懶惰而且不容易掌握概念的同志也能夠在幾分鐘內瞭解這個概念。再次強調,我向大家 100% 地保證。經過學習 Java8 的 Stream 可讓你很快地學會 Rx。好,咱們開始了。
Stream:
支持在元素造成的流上進行函數式操做(好比在集合上進行的 map-reduce 變換)的類(docs.oracle)。
第一個問題:在英語中 Stream 是什麼意思?
答案:一條很窄的小河,或者源源不斷流動的液體、空氣、氣體。在編程的時候把數據轉化成「流」的形式,好比我有一個字符串,可是我想把它變成「流」來使用的話我須要幹些什麼,我須要建立一個機制,使這個字符串知足「源源不斷流動的液體、空氣、氣體 {或者數據}」的定義。問題是,咱們爲何想要本身的數據變成「流」呢,下面是個簡單的例子。
就像下面這幅圖中畫的那樣,我有一杯混合着大大小小石子的藍色的水。
如今按照咱們關於「流」的定義,我用下圖中的方法將水轉化成「流」。
爲了讓水變成水流,我把水從一個杯子倒進另外一個杯子 裏。如今我想去掉水中的大石子,因此我造了一個能夠幫我濾掉大石子的過濾器。「大石子過濾器」以下圖所示。
如今,將這個過濾器做用在水流上,這會獲得不包含大石子的水。以下圖所示。
哈哈哈。 接下來,我想從水中清除掉全部石子。已經有一個過濾大石子的過濾器了,咱們須要造一個新的來過濾小石子。「小石子過濾器」以下圖所示。
像下圖這樣,將兩個過濾器同時做用於水流上。
哇哦~ 我已經感受到大家領悟了我說的在編程中使用流所帶來的好處是什麼了。接下來,我想把水的顏色從藍色變成黑色。爲了達到這個目的,我須要造一個像下圖這樣的「水顏色轉換器(mapper)」。
像下圖這樣使用這個轉換器。
把水轉換成水流後,咱們作了不少事情。我先用一個過濾器去掉了大石子,而後用另外一個過濾器去掉了小石子, 最後用一個轉換器(map)把水的顏色從藍色變成黑色。
當我將數據轉換成流時,我將在編程中獲得一樣的好處。如今,我將把這個例子轉換成代碼。我要顯示的代碼是真正的代碼。可能示例代碼不能工做,但我將要使用的操做符和 API 是真實的,咱們將在後面的實例中使用。因此,同志們不要把關注點放在編譯上。經過這個例子,我有一種感受,咱們將很容易地把握這些概念。在這個例子中,重要的一點是,我使用 Java8 的 Stream API 而不是 Rx API。我不想讓事情變困難,但稍後我也會使用 Rx。
圖像中的水 & 代碼中的水:
public static void main(String [] args){
Water water = new Water("water",10, "big stone", 1 , "small stone", 3);
// 含有一個大石子和三個小石子的十升水
for (String s : water) {
System.out.println(s);
}
}複製代碼
輸出:
water
water
big stone
water
water
small stone
water
small stone
small stone
water
water
water
water
water
圖像中的水流 & 代碼中的水流:
public static void main(String[] args) {
Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
// 10 litre water with 1 big and 3 small stones.
water.stream();
}
//輸出和上面那個同樣複製代碼
圖像中的「大石子過濾器」 & 代碼中的「大石子過濾器」:
同志們這裏須要注意下!
在 Java8 Stream 中有個叫作 Predicate(謂詞,能夠判斷真假,詳情見離散數學中的相關定義——譯註)的函數式接口。因此,若是我想進行過濾的話,能夠用這個函數式接口實現流的過濾功能。如今,我給你們展現在咱們的代碼中如何建立「大石子過濾器」。
private static Predicate<String> BigStoneFilter = new Predicate<String>() {
@Override
public boolean test(String s) {
return !s.equals("big stone");
}
};複製代碼
正如咱們在 第三部分 所學到的,任何函數式接口均可以轉換成 Lambda 表達式。把上面的代碼轉換成 Lambda 表達式:
private static Predicate<String> BigStoneFilter = s -> !s.equals("big stone");複製代碼
圖像和代碼中的做用在水流上的「大石子過濾器」:
public static void main(String[] args) {
Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
water.stream().filter(BigStoneFilter)
.forEach(s-> System.out.println(s));
}
private static Predicate<String> BigStoneFilter = s -> !s.equals("big stone");複製代碼
這裏我使用了 forEach 方法,暫時把這看成流上的 for 循環。用在這裏僅僅是爲了輸出。除去沒有這個方法,咱們也已經實現了咱們在圖像中表示的內容。是時候看看輸出了:
water
water
water
water
small stone
water
small stone
small stone
water
water
water
water
water
沒有大石子了,這意味着咱們成功過濾了水。
圖像中的「小石子過濾器」 & 代碼中的「小石子過濾器」:
private static Predicate<String> SmallStoneFilter = s -> !s.equals("small stone");複製代碼
在圖像和代碼中使用「小石子過濾器」:
public static void main(String[] args) {
Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
water.stream()
.filter(BigStoneFilter)
.filter(SmallStoneFilter)
.forEach(s-> System.out.println(s));
}
private static Predicate<String> BigStoneFilter = s -> !s.equals("big stone");
private static Predicate<String> SmallStoneFilter = s -> !s.equals("small stone");複製代碼
我不打算解釋 SmallStoneFilter,它的實現和 BigStoneFilter 是同樣同樣的。這裏我只展現輸出。
water
water
water
water
water
water
water
water
water
water
圖像中的「水顏色轉換器」 和 代碼中的「水顏色轉換器」
同志們這裏須要注意!
在 Java8 Stream 中有個叫作 Function 的函數式接口。因此,當我想進行轉換的時候,須要把這個函數式接口送到流的轉換(map)函數裏面。如今,我給你們展現在咱們的代碼中如何建立「水顏色轉換器」。
private static Function<String, String > convertWaterColour = new Function<String, String>() {
@Override
public String apply(String s) {
return s+" black";
}
};複製代碼
這是一個函數式接口,因此我能夠把它轉換爲 Lambda :
private static Function<String, String > convertWaterColour = s -> s+" black";複製代碼
簡單來講,泛型中的第一個 String 表明我從水中獲得什麼,第二個 String 表示我會返回什麼。 爲了更好地掰扯清楚,我寫了個把 Integer 轉化成 String 的轉換器。
private static Function<Integer, String > convertIntegerIntoString = i -> i+" ";複製代碼
回到咱們原來的例子。
爲水流添加顏色轉換器的圖像和代碼:
public static void main(String[] args) {
Water water = new Water("water", 10, "big stone", 1, "small stone", 3);
water.stream()
.filter(BigStoneFilter)
.filter(SmallStoneFilter)
.map(convertWaterColour)
.forEach(s -> System.out.println(s));
}
private static Predicate<String> BigStoneFilter = s -> !s.equals("big stone");
private static Predicate<String> SmallStoneFilter = s -> !s.equals("small stone");
private static Function<String, String> convertWaterColour = s -> s + " black";複製代碼
輸出:
water black
water black
water black
water black
water black
water black
water black
water black
water black
water black
完活!如今咱們再次回顧一些內容。
filter(過濾器): Stream 有一個只接受 Predicate 這個函數式接口的方法。咱們能夠在 Predicate 裏寫做用在數據上的邏輯代碼。
map(映射):Stream 有一個只接受 Function 這個函數式接口的方法。咱們能夠在 Function 裏寫按照咱們的要求轉換數據的邏輯代碼。
在進入下個環節以前,我想解釋一個曾經困惑我好久的東西。當咱們在任意數據上使用 stream() 的時候,背後是怎樣工做的。因此我要舉一個例子。我有一個整數列表。我想在控制檯上顯示它們。
public static void main(String [] args){
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
}複製代碼
使用命令式編程來打印數據:
public static void main(String [] args){
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (Integer integer : list) {
System.out.println(integer);
}
}複製代碼
使用 Stream 或 Rx 的方式來打印數據:
public static void main(String [] args){
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.stream().forEach(integer -> System.out.println(integer));
}複製代碼
對於以上兩段代碼,它們的不一樣點在哪呢?
簡單來講,在第一段代碼中我本身管理 for 循環:
for (Integer integer : list) {
System.out.println(integer);
}複製代碼
可是在第二段代碼中,流(或者稍後後要展現的 Rx 中的 Observable)進行循環:
list.stream().forEach(integer -> System.out.println(integer));複製代碼
我認爲不少事情都說清楚了,是時候用 Rx 來寫個真實的例子了。在這個例子中,我會同時使用流式編碼(stream code)和響應式編碼(Rx code),這樣你們能夠更容易地掌握這倆的概念。
2. Java8 Stream to Rx Observable:
有一個存有 「Hello World」 的列表。 在圖片中,把它視做字符串。在代碼中把它看做列表,這樣比較好解釋。
Java8 的 Stream 代碼:
public static void main(String [] args){
List<String> list = new ArrayList<>();
list.add("H");
list.add("e");
list.add("l");
list.add("l");
list.add("o");
list.add(" ");
list.add("W");
list.add("o");
list.add("r");
list.add("l");
list.add("d");
list.stream(); // Java8
}複製代碼
Android 中的代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<String> list = new ArrayList<>();
list.add("H");
list.add("e");
list.add("l");
list.add("l");
list.add("o");
list.add(" ");
list.add("W");
list.add("o");
list.add("r");
list.add("l");
list.add("d");
Observable.fromIterable(list);
}
}複製代碼
在這裏展現了 Java8 代碼和 Android 代碼。從如今開始,我只給出代碼中的響應式(Reactive)部分而不給出完整的一個類。完整代碼分享在文章的最後了。上面的代碼將變成這樣:
Again above example:
list.stream(); // Java8
Observable.fromIterable(list); // Android複製代碼
這二者會有相同的結果,這樣來輸出整個列表:
list.stream()
.forEach(s-> System.out.print(s)); // Java8
Observable.fromIterable(list)
.forEach(s-> Log.i("Android",s)); // Android
Java8 的輸出:
Hello World
Android 的輸出:
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: H
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: e
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: l
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: l
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: o
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android:
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: W
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: o
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: r
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: l
03-12 15:55:33.561 6094-6094/async.waleed.rx I/Android: d複製代碼
是時候來比較下這倆了。
list.stream().forEach(s-> System.out.print(s)); // Java8
Observable.fromIterable(list).forEach(s-> Log.i("Android",s)); // Android複製代碼
在 Java8 中我想要一個東西變成流的形式,我會用 Stream 的 API,可是在 Android 裏,我先把那個東西轉換成 Observable 而後獲取到數據流。
接下來,咱們將用 ’l‘ 做爲過濾器來處理 Hello World,就像下面這樣:
In code:list.stream()
.filter(s -> !s.equals("l"))
.forEach(s-> System.out.print(s)); //Java8
Observable.fromIterable(list)
.filter(s->!s.equals("l"))
.forEach(s-> Log.i("Android",s)); // Android
輸出 in Java8:
Heo Word
輸出 In Android:
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: H
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: e
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: o
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android:
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: W
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: o
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: r
03-12 16:05:58.558 10236-10236/async.waleed.rx I/Android: d複製代碼
好。是時候對 Java8 的 Stream API 說再見了。
3. RxJava2 的 Android 示例:
有一個整數數組,我想讓數組中的每一個成員變成自身的平方。
如圖所示:
Android 代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Integer[] data = {1,2,3,4};
Observable.fromArray(data)
.map(value->value*value)
.forEach(value-> Log.i("Android",value+""));
}複製代碼
輸出:
03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 1
03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 4
03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 9
03-12 16:13:32.432 14918-14918/async.waleed.rx I/Android: 16
.map(value->value*value)複製代碼
這波很穩,咱們以前已經用到過相同的概念了。把一個函數式接口傳進 map,這個函數簡單地將輸入的數平方後返回。
.forEach(value-> Log.i("Android",value+""));複製代碼
稍有常識的人都知道,咱們只能在 log 中打印字符串。在上面的代碼中,我在整數值的後面添加 +""
來把他們轉換成字符串。
哇哦!咱們能夠在這個例子中再用一次 map。大家都知道我須要把整數轉換成字符串以便打印到 Logcat,可是我如今打算爲 map 再寫一個函數式接口來完成轉換。這意味着咱們不須要在數據後面添加 +""
了,以下所示:
Observable.fromArray(data)
.map(value->value*value)
.map(value-> Integer.toString(value))
.forEach(string-> Log.i("Android",string));複製代碼
4. 如何把命令式代碼轉化成 RxJava2 Android 代碼:
這裏我打算使用一段現實存在於某 APP 的代碼,我將使用 Rx Observable 把它轉化成響應式(Reactive)代碼。這樣你很容易就知道怎樣開始在本身的項目中使用 Rx 了。重要的東西可能不是很容易理解,但你應該開始動手,這樣纔會感受良好。因此,像我在示例代碼中提到的那樣去使用它們,我會在下一篇文章中詳細解釋。嘗試多去練練手。
示例:
我在一個項目中使用了 OnBoarding 界面,根據 UI 設計須要在每一個 OnBoarding 界面上顯示點點,以下圖所示:
命令式編程的代碼:
private void setDots(int position) {
for (int i = 0; i < mCircleImageViews.length; i++) {
if (i == position)
mCircleImageViews[i].setImageResource(R.drawable.white_circle_solid_on_boarding);
else
mCircleImageViews[i].setImageResource(R.drawable.white_circle_outline_on_boarding);
}
}複製代碼
響應式代碼(Rx)的代碼:
public void setDots(int position) {
Observable.fromIterable(circleImageViews)
.subscribe(imageView ->
imageView.setImageResource(R.drawable.white_circle_outline_on_boarding));
circleImageViews.get(position)
.setImageResource(R.drawable.white_circle_solid_on_boarding);
}複製代碼
在 setDots 函數中,我簡單地遍歷每一個 ImageView 而且把它們設置成白色的空心圈,以後將選定的 ImageView 從新設定爲實心圈。
或者,
public void setDots(int position) {
Observable.range(0, circleImageViews.size())
.filter(i->i!=position)
.subscribe(i->circleImageViews.get(i).setImageResource(R.drawable.white_circle_outline_on_boarding)));
circleImageViews.get(position)
.setImageResource(R.drawable.white_circle_solid_on_boarding);
}複製代碼
在這個 setDots 函數中,我把除選定的 ImageView 以外的全部 ImageView 設置爲白色空心圈。
以後,將選中的 ImageView 設置爲實心圈。
4. 幾個關於把命令式代碼轉換成響應式代碼的技巧:
爲了讓你們能夠在現有的代碼上輕鬆開始使用 Rx,我寫了幾個小技巧。
for (int i = 0; i < 10; i++) {
}
==>
Observable.range(0,10);複製代碼
for (int i = 0; i < 10; i++) {
if(i%2==0){
Log.i("Android", "Even");
}
}
==>
Observable.range(0,10)
.filter(i->i%2==0)
.subscribe(value->Log.i("Android","Event :"+value));複製代碼
public class User {
String username;
boolean status;
public User(String username, boolean status) {
this.username = username;
this.status = status;
}
}
List<User> users = new ArrayList<>();
users.add(new User("A",false));
users.add(new User("B",true));
users.add(new User("C",true));
users.add(new User("D",false));
users.add(new User("E",false));
for (User user : users) {
if(user.status){
user.username = user.username+ "Online";
}else {
user.username = user.username+ "Offline";
}
}複製代碼
在 Rx 中,有不少方法實現上述代碼。
使用兩個流:
Observable.fromIterable(users)
.filter(user -> user.status)
.map(user -> user.username + " Online")
.subscribe(user -> Log.i("Android", user.toString()));
Observable.fromIterable(users)
.filter(user -> !user.status)
.map(user -> user.username + " Offline")
.subscribe(user -> Log.i("Android", user.toString()));複製代碼
在 map 中使用 if else :
Observable.fromIterable(users)
.map(user -> {
if (user.status) {
user.username = user.username + " Online";
} else {
user.username = user.username + " Offline";
}
return user;
})
.subscribe(user -> Log.i("Android", user.toString()));複製代碼
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
System.out.print("j ");
}
System.out.println("i");
}
==>
Observable.range(0, 10)
.doAfterNext(i-> System.out.println("i"))
.flatMap(integer -> Observable.range(0, 10))
.doOnNext(i -> System.out.print("j "))
.subscribe();複製代碼
這裏用到了 flatmap 這個新的操做符。先僅僅嘗試像示例代碼中那樣使用,我會在下篇文章中解釋。
總結:
同志們幹得好!今天咱們學 Rx Android 學得很開心。咱們從圖畫開始,而後使用了 Java8 的流(Stream)。以後將 Java8 的流轉換到 RxJava 2 Android 的 Observable。再以後,咱們看到了實際項目中的示例而且展現了在現有的項目中如何開始使用 Rx。最後,我展現了一些轉換到 Rx 的技巧:把循環用 forEach 替換,把 if 換成 filter,用 map 進行數據轉化,用 flatmap 代替嵌套的循環。下篇文章: Dialogue between Rx Observable and a Developer (Me) [ Android RxJava2 ] ( What the hell is this ) Part5.
但願大家開心,同志們再見!
代碼:
對於其餘全部示例,您可使用文章中的片斷。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。