零基礎理解RxJava和響應式編程

這是一篇能幫助你在10分鐘內從零到一理解RxJava和響應式編程的文章。

RxJava發展到現在已經在2016年推出了第二代。可能你聽說過很多人講起RxJava,但是很少在實際項目開發中用到它。 原因很簡單,RxJava雖然很好用,但是它有一定的學習成本。很多人只是知道這麼個東西,但是沒有真正的去學習和推動RxJava。畢竟會覺得即使沒有RxJava也一樣能寫好代碼。 其實它的學習成本和帶來的收益對比的話,是非常值得花點時間去學的。當你切換到Rx編程思維之後,會發現很多以前難以處理的問題在響應式編程下都變得易如反掌。 而很多公司沒有推進RxJava的原因,主要在於船大難掉頭。筆者見過一個上億日活的項目,至今還在用ant構建。可想而知還有許多新技術受限於項目的歷史原因沒法應用。 另一個推動RxJava困難的原因在於開發團隊水平層次不齊。如果你的團隊裏有成員連併發和線程都搞不清楚的話,RxJava可能只能帶來負面效果。

那爲什麼還是越來越多的人在談論RxJava呢,這要從響應式編程說起。

響應式編程

只要你有一些開發經驗就有體會,不管是什麼系統,幾乎都不可能用同步操作來編寫代碼。放在Android上更是這樣。 用戶交互,屏幕旋轉,網絡請求,這些都是異步源。 異步源的存在讓傳統的命令式編程變的複雜,雖然Java是變向對象語言,但某種意義上它還是命令式編程。想象一下這麼個場景,你需要等到用戶輸入搜索key後,開始網絡請求,並把返回結果顯示到UI上,而此時用戶可能還會旋轉屏幕。要完成這麼個需求,你可以想象下要寫多少個handler去做線程間的切換。而這些handler就是我們開發者需要去介入的代碼邏輯,也是我們頭痛的地方。

響應式編程的英文是Reactive Programming。回想一下開發中的listener,可以認爲是響應式編程的例子,某個按鈕被點擊,listener回調,然後發起網絡請求,網絡請求可以看成是按鈕點擊的響應。而網絡請求返回數據,到數據更新到UI上,也是一種響應。

RxJava的核心思想在這裏,通常我們需要編寫大量代碼來控制響應的邏輯,有時候還需要處理一些異常情況,比如請求失敗,數據爲空的處理,也有可能用戶已經切換出當前界面,這時候就需要處理一下把數據更新到一個已經被回收的UI上可能發生的問題。這些網絡請求,UI互動,數據庫,磁盤IO,都可以看成異步源,而Android本身是也是一個大異步源,像Push通知,屏幕旋轉,鎖屏開屏這些操作都會觸發異步行爲。Reactive編程認爲我們不應該介入異步源之間的響應和交互,而應該直接的把異步源和響應綁定起來。

用響應式的思路寫代碼

我們從一個簡單的例子開始,UserManager用來定義用戶名字/年紀,也可以獲取用戶對象

interface UserManager {
    User getUser();
    void setName(String name);
    void setAge(int age);
}

UserManager um = new UserManager();
System.out.println(um.getUser());

um.setName("Jane Doe");
System.out.println(um.getUser());

這段代碼沒什麼問題,但當它以異步的形式發展的話問題就來了。假設setName是個異步操作,它往服務器提交了個String。作爲開發者可以選擇相信服務器每次提交都是成功的(並不可能),提交完成後照樣println輸出。也可以選擇靠譜的方式,在服務器提交成功後才輸出本地。 我們可以在每次提交的時候設置個runnable作爲成功的回調,這是很簡單的響應式編程思想,確保網絡成功的時候纔去更新數據。

interface UserManager {
    User getUser();
    void setName(String name, Runnable callback);A
    void setAge(int age, Runnable callback);B
}
UserManager um = new UserManager();
System.out.println(um.getUser());

um.setName("Jane Doe", new Runnable() {
    @Override public void run() {
        System.out.println(um.getUser());
    }
});

但這裏並沒處理網絡失敗的情況,更完善的代碼需要像下面這樣子,增加對錯誤處理的響應,

um.setName("Jane Doe", new UserManager.Listener() {
    @Override public void success() {
        System.out.println(um.getUser());
    }

    @Override public void failure(IOException e) {
        // TODO show the error...
    }
});

你作爲開發者可以選擇在failure的時候做響應,比如彈出提示,自動重新請求。在傳統的命令式編程中不可避免地需要在單線程邏輯中混入這些異步代碼。

而且在Android開發中這些都發生在Context裏,比方Activity,我們把這段代碼放到Activity裏去看看,

public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();

    @Override
  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.user);
        TextView tv = (TextView) findViewById(R.id.user_name);
        tv.setText(um.getUser().toString());

        um.setName("Jane Doe", new UserManager.Listener() {
          @Override
      public void success() {
              tv.setText(um.getUser().toString());
          }
          @Override
      public void failure(IOException e) {
              // TODO show the error...
              }
        });
    }
}

我們現在還沒考慮線程的問題。像更新UI的操作,衆所周知需要在UI線程進行,上面的網絡請求代碼 setName 可能在子線程返回,我們需要讓這些地方跳回到主線程去。

public final class UserActivity extends Activity {
    private final UserManager um = new UserManager();

    @Override
  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.user);
        TextView tv = (TextView) findViewById(R.id.user_name);
        tv.setText(um.getUser().toString());

        um.setName("Jane Doe", new UserManager.Listener() {
            @Override
      public void success() {
                runOnUiThread(new Runnable() {
                    @Override public void run() {
                        if (isDestroyed()) {
                            tv.setText(um.getUser().toString());
                        }
                    }
                });
            }
            @Override
      public void failure(IOException e) {
                // TODO show the error...
            }
        });
    }
}

到這裏我們還沒有考慮更深的問題,比如TextView在回調中是否被回收了。因爲這是個異步操作,不能指望網絡請求結束時還處於剛纔的界面。 你看這堆東西,我們用一大段的代碼只是保證了一個簡單的UI更新操作,並且還沒有考慮實際場景中的問題。一旦把它投入到實際應用中,你就需要處理一堆繁瑣的代碼。 傳統的響應式代碼繁瑣,而且閱讀麻煩,卻又不可避免。但你只需要記住這種異步源+響應的思路,就能輕鬆地切換到RxJava的世界。

Observable和Observer

RxJava中的其中一個重點對象是Observable-被觀察者。如果用上面的例子來理解,網絡請求就是一個Observable。沒錯,你可以把所有的異步源都看成Observable。 Reactive編程中另外一個重點對象是 Observer,它要觀察,和響應Observable發生的事情,進而做出對應的響應。在上面的例子中,println事件就是一種響應,而UI更新,tv.setText也是一種響應。

概括起來RxJava就是用Observer和Observable把響應和異步源包裹起來,然後通過 subscribe(訂閱),將兩者進行綁定。一段綁定異步源和響應的代碼像下面這樣,不過下面這段代碼沒有任何的現實意義,只是用來展示綁定的過程,

Observable.create(new Observable.OnSubscribe<String>() {
      @Override
      public void call(final Subscriber<? super String> subscriber) {
           //TODO
      })
      .subscribe(new Observer<String>() {
            @Override
            public void onCompleted() {
                //TODO
            }

            @Override
            public void onError(Throwable e) {
                //TODO
            }

            @Override
            public void onNext(String s) {
                //TODO
            }
        });

我們這裏的代碼用的是RxJava作爲例子,在RxJava2的時候相關的接口改了一些,雖然名字變了,不過思路還是一樣。

如果你是第一次接觸鏈式調用,可能這段代碼看起來不容易理解,我們轉換成下面這樣

Observable observable = Observable.create(new Observable.OnSubscribe(){...});
Observer observer = new Obvserver(){...};
observable.subscribe(observer);

在RxJava中,異步源作爲被觀察者被 Observable包括起來,它的邏輯要寫到 call 方法裏,當 subscribe被調用的時候會觸發 call。 observer則是響應。這裏在call方法的參數裏出現了一個新的對象 subscriber,這其實就是 Observer,Observer是個接口,subscriber是實現了這個接口的類。Observer有三個方法分別用來作爲異步操作完成後的回調,

public interface Observer<T> {
    void onCompleted();

    void onError(Throwable var1);

    void onNext(T var1);
}

從名字上立馬能理解的是onError,那就是異步失敗了。 onCompleted() 則是用來表示異步操作結束, onNext(T var1) 用來把異步操作結果回傳給 observer的 onNext(T value) 方法。 這裏有個點要注意,onComplted調用之後再調用 onNext的話,observer的onNext也不會被調動,在RxJava中對於onCompleted的定義就是操作已經完成,此時應該要回收掉observer。有興趣的話可以深入去看看RxJava的源碼。

RxJava進階

關於RxJava還有很多點沒有說,比如變換的概念,因爲篇幅的關係這些我們下一次再繼續講。 這篇文章的重點在於理解Reactive編程思維,將我們的命令式編程的習慣切換到響應式編程上來。如果你已經能輕鬆的用Reactive思維看代碼的話,恭喜你已經入門了,RxJava寫的代碼現在你也能輕易地理解。只要再花點時間,就能輕鬆的在你的項目中應用上RxJava了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章