RxJava:自己動手擼一個RxBinding(一)。

Lesincs發表於2018-05-04

RxJava:自己動手擼一個RxBinding(一)。

最近在專案中使用到了JakeWharton大佬的RxBinding,在某個表單驗證的模組中發現運用RxBinding能得到很好的效果,既減小了程式碼量,邏輯也更清晰。感嘆它的神奇之處,於是檢視了下原始碼,發現原始碼並不是很複雜,於是決定跟著原始碼自己動手擼一個簡易版RxBinding,加深對RxJava的理解。

預計實現兩個目標:

  • View點選事件RxJava形式實現
  • TextView以及子類的的文字變化RxJava實現

先實現目標一。 大致思路:為view設定監聽器,當每次點選事件到來時,向下遊傳送事件。

我們看下原版RxBinding是怎麼實現點選事件訂閱的。

 RxView.clicks(tv_xx).subscribe({
        ....dosomething()
        })
複製程式碼

可以很清楚的知道,RxView.clicks(View view)這個方法返回的是一個可被訂閱的observable,我們看引數傳入了需要被訂閱點選事件view的引用,可以猜想這個observableview肯定有某種py,先不管。

依樣畫葫蘆,原版RxBinding是通過靜態方法返回可被訂閱的的observable,我們也照著寫就行了,本著學習的心態,不必在這些地方上糾結,重要的是擬清思路。

public class RxView {

    /**
     * 這裡肯定是返回一個可被訂閱的observable
     * @param view 需要被訂閱點選事件的view
     * @return
     */
    public static Observable clicks(View view) {
        return null;
    }

}

複製程式碼

程式碼很簡單,就是一個靜態方法返回一個observable,因為我們還沒寫對應的observable,所以這裡暫時先返回null。

接下來就要寫我們的observable了,首先我們的observable肯定是和view有著密不可分的關係的,所以肯定得持有view的引用,所以先這樣寫: 新建一個observable,命名為ViewClickObservable,繼承自Observable,並在構造中傳入view,然後儲存.

public class ViewClickObservale extends Observable {

    private View mView;

    public ViewClickObservale(View mView) {
        this.mView = mView;
    }

    /**
     * 繼承Observable必須實現的抽象方法
     * @param observer
     */
    @Override
    protected void subscribeActual(Observer observer) {

    }

}
複製程式碼

我們看到程式碼中多了個subscribeActual(Observer observer)方法,這是Observable類中的抽象方法,必須重寫,該方法在observable被下游訂閱時會被呼叫。而引數傳入的observer,就是我們的發射器了。有的人可能這裡有疑問了,observer不是觀察者嗎,怎麼又變成發射器了,發射事件不是被觀察者做的事情嗎?如果以以往的固定思維,到這裡是有點轉不過圈子。但是如果你深入過RxJava,你會發現,它只不過是通過Java語法的各種包裝,鏈式呼叫,形成了一種響應式風格而已.舉個例子,如果你平常使用RxJavaObserveronNext()方法中收到一個事件,你腦子中形成的模型肯定是上游發射了一個事件到下游,然後observer在onNext()接受到了這個事件,可是本質上卻是內部呼叫了最上游的observeronNext()方法而已,這裡之所以說最上游的observer是因為中間可能用到各種操作符,而這些操作符起到的作用就是對最下游的observer進行層層包裝改造,或者限制,最終傳到源頭的Observable,然後用這個被包裝改造後的observer發射事件,因為每個操作符的包裝改造效果不同,所以下游能收到不同的效果.

大致知道了原理,我們就可以繼續改造我們的ViewClickObservale類了,我們知道了傳入的observer為發射器,那麼事件從哪裡來呢?肯定還是隻能從點選事件了.每次收到一個點選事件,我們就向下遊發射一個事件就行了,繼續加程式碼.

public class ViewClickObservale extends Observable<Object> {

    private View mView;

    public ViewClickObservale(View mView) {
        this.mView = mView;
    }

    /**
     * 繼承Observable必須實現的抽象方法
     * @param observer
     */
    @Override
    protected void subscribeActual(final Observer observer) {
        
        //為view設定監聽,每次收到點選事件,向下遊發射事件
        mView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                observer.onNext("onClick");
            }
        });
    }

}
複製程式碼

這是編譯器提示未給Observer指定泛型,可能存在隱患的安全問題.我第一次看到這個提示是這想的,既然是點選事件,下游不關心事件本身,傳送一個"onClick"(String型別)就行了,因為StringObject子類,所以泛型指定為這樣:

//泛型指定為Object子類
protected void subscribeActual(final Observer<? extend Object> observer)
複製程式碼

不料卻報錯了,看了下報錯原因是Observer強迫指定泛型為<? super T>型別,然後我改成這樣就沒問題了.

protected void subscribeActual(final Observer<? super Object> observer)
複製程式碼

我感覺很奇怪,我想傳送事件的是String型別,是Object的子類,應該使用<? extend Object>啊,然而使用<? super Object>才是正確的。 思考了半天終於明白:

  • 使用<? extend Object>表示型別為Object以及子類,表示一個未知的型別,可能是Int,也能是String,編譯器不知道你具體指定的型別,所以不允許你傳送String,因為你泛型也可以指定為Int或者其它.
  • 使用<? super Object>表示具體型別只能為Object以及父類,因為Object沒有父類,也就是隻能是Object,String和Int都是Object子類,所以傳送String或者Int都是沒問題的,Object型別的引用均能指向他們.

寫好了之後修改RxView類的程式碼

public class RxView {

    /**
     * 這裡肯定是返回一個可被訂閱的observable
     *
     * @param view 需要被訂閱點選事件的view
     * @return
     */
    public static Observable<Object> clicks(View view) {
        return new ViewClickObservale(view);
    }
    
}
複製程式碼

不再返回null,返回我們剛寫好的Observable.然後進行測試.

MainActivity中的程式碼:

public class MainActivity extends AppCompatActivity {

    String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RxView.clicks(findViewById(R.id.tv_obs))
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        Log.d(TAG, "我被點選了");
                    }
                });
    }
}

複製程式碼

佈局中就一個TextView,我點選了三次,列印出的日誌如下:

05-03 23:55:42.143 1770-1770/april.lesincs.rxbinding_demo D/MainActivity: 我被點選了
05-03 23:55:43.068 1770-1770/april.lesincs.rxbinding_demo D/MainActivity: 我被點選了
05-03 23:55:43.780 1770-1770/april.lesincs.rxbinding_demo D/MainActivity: 我被點選了
複製程式碼

測試成功!一個簡易版的RxBinding就擼出來了,程式碼不多,但是需要對RxJava有一定理解才能知道為啥要這麼寫,原版中還多了取消訂閱以及主執行緒檢測的功能,可以說是十分嚴謹了,我這裡就不繼續擼了,想了解的可以去看看原始碼.

下篇準備實現第二個目標:TextView以及子類的的文字變化RxJava實現,敬請期待.

相關文章