RxJava從放棄到入門(一):基礎篇

xiasuhuei321發表於2017-12-13

寫在前面

RxJava我一直是很想用的,扔物線老師的文章我也看了一點,但是說實話,其中很多東西交錯在一起,對於我來說有點難以理解。而且看很多文章總是看了前面忘後面,還有一些結合lambda講的,說實話,我是懵逼的。在這裡把我自己對於RxJava的一些理解,看到的一些好文記錄下來。

RxJava是啥

Rx是一個函式庫,讓開發者可以利用可觀察序列和LINQ風格查詢操作符來編寫非同步和基於事件的程式。

好,對於C#不怎麼了解的人一般不會知道LINQ是啥東東吧……這個介紹我們先選擇略過。看看github上RxJava是怎樣描述自己的。

**  a library for composing asynchronous and event-based programs by using observable sequences. ** 一個在Java VM上使用可觀測的序列來組成非同步的、基於事件的程式的庫。

這句話對於還未用過、看過RxJava的人來說是比較難理解的,好在關於RxJava的資料非常多,我們可以站在巨人的肩膀上來總結。首先扔物線對於RxJava給出的關鍵詞就是** 非同步 **,歸根到底它就是一個實現非同步操作的庫。而回過頭來,再看一遍這個定義,我們可以看出另外兩個關鍵詞:可觀測的序列、基於事件,你可能會說這不廢話嗎,這句話一共才幾個詞,都快給我說完了。沒錯,因為這句話概括的非常精準,讓人難以再精簡了。

為啥要用RxJava

Android中實現非同步的工具還是有的,那麼問題來了,對於我們Android開發者來說,為什麼要用RxJava而不是本來的工具?

Talk is cheap,下面選取部分我以前寫的程式碼,用AsyncTask實現的載入資料的類:

 class DownloadTask extends AsyncTask<String, Integer, ArrayList<ImageBean>> {

        private ObjectOutputStream oos;

        @Override
        protected ArrayList<ImageBean> doInBackground(final String... params) {
            try {
                String imageUrl = params[0];
                HttpUtils.getJsonString(imageUrl, new HttpUtils.HttpCallbackListener() {
                    @Override
                    public void onFinish(String response) {
                        if (JsonUtils.readJsonImageBean(response) != null) {
                            imageList = JsonUtils.readJsonImageBean(response);
//                            memoryCache.addArrayListToMemory("imageList", imageList);

                            if (count == 0) {
                                //序列化imageList
                                if (getActivity() != null) {
                                    File imageCache = FileUtils.getDisCacheDir(getActivity(), "ImageBean");
                                    try {
                                        oos = new ObjectOutputStream(new FileOutputStream(imageCache));
                                        oos.writeObject(imageList);
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    } finally {
                                        if (oos != null) {
                                            try {
                                                oos.close();
                                            } catch (IOException e) {
                                                e.printStackTrace();
                                            }
                                        }
                                    }
                                }
                                count++;
                            }
                        }
                    }

                    @Override
                    public void onError(Exception e) {
                        e.printStackTrace();
                    }
                });
                return imageList;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
複製程式碼

上面那段程式碼表示先從檔案中讀取list,然後再從網路獲取資料(初學時的程式碼,沒有考慮好一些邏輯關係)。當我打算重構程式碼,看到這一段的時候,我的內心是崩潰的。雖然邏輯並不複雜,但是這些迷之縮排實在是看的蛋疼。那麼如果我用RxJava重寫一下上面的邏輯,會是怎樣的呢?

        Observable.just(0, 1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<Integer, String>() {
                    @Override
                    public String call(Integer integer) {
                        if (integer == TYPE_NETWORK) {
                            return getUrl(pageIndex, type);
                        }
                        return "cache";
                    }
                }).subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                e.printStackTrace();
            }

            @Override
            public void onNext(String s) {
                if (s.equals("cache")) {
                   //載入快取
                    
                } else {
                   //從網路獲取資料
                   onCompleted();
                    };
                }
            }
        });
複製程式碼

以上程式碼實現的非常不科學,非常的不RxJava,但是在這裡僅僅是作為一個示例,讓你感受一下RxJava的特性:簡潔。你可能會說這哪裡簡潔了啊?程式碼量不跟以前差不多嗎,是的,甚至有的時候程式碼量還會增加一點,但是這樣的程式碼能讓你感覺到清晰的邏輯。一切邏輯都在鏈子裡了,而且如果你使用lambda會得到更加簡潔的程式碼。。

這就是我們要用RxJava的原因之一了:** 簡潔 **

這裡的簡潔不是指程式碼量的少,而是指程式碼邏輯的簡潔。而且這種優勢隨著邏輯的複雜而更加明顯。在這裡我並不會將lambda和RxJava結合在一起,一是因為自己的確不熟,二也是因為自己初接觸RxJava,對於我這種入門級選手還是要先排除一些干擾項

RxJava核心&基礎

在開始擼RxJava的程式碼之前,我們首先要弄清楚RxJava中的三個基本也是核心的概念:觀察者(Observer)、訂閱(Subscribe())和被觀察者(Observable)。熟悉設計模式的你可能會立刻想到,這不就是觀察者模式嗎。是的,就是觀察者模式。

訂閱.png

觀察者模式定義了物件間一種一對多的依賴關係,每當一個物件狀態發生改變,所有依賴於它的物件都會得到通知並被自動更新。在Android中比較經典的例子有Button的點選,只有當Button被點選的時候,觀察者OnClickListener在Button的點選狀態發生改變時將點選事件傳送給註冊的OnClickListener。而對於RxJava來說也是如此,接下來我將換一種我喜歡的描述來講解我所理解的RxJava。RxJava中Observable是發射資料的源,無論他是“熱”啟動還是“冷”啟動,總之,他最終都是用來發射資料的。Observer則是資料接收者,而Observer和Observable則通過subscribe()(訂閱)結合在一起,從而達到Observer接收Observable發射的資料的目的。

在講解完了RxJava的核心之後,還需要注意一些細節: 在RxJava的文件中指出,無論哪種語言,你的觀察者(Observer)需要實現以下方法的子集:

  • onNext(T item) Observable呼叫這個方法發射資料,方法的引數就是Observable發射的資料,這個方法呼叫次數取決於實現。

  • onError(Exception e) Observable遇到錯誤時會呼叫這個方法,這個呼叫會終止Observable,onError和以下將要介紹的onComplete是互斥的,即同一個事件序列中二者只能有一個被呼叫。

  • onComplete() 正常終止

好了,煩人的概念時間終於過去了,讓我們開始愉悅的Hello World時間!

Hello World!

打碼之前記得加上依賴:

compile 'io.reactivex:rxjava:1.0.14' compile 'io.reactivex:rxandroid:1.0.1'

我這的依賴好像還是看扔物線文的時候新增的……應該比較老了……

話不多說直接上碼:

        //建立被觀察者
        Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                //回撥
                subscriber.onNext("Hello");
                subscriber.onNext("World");
                subscriber.onNext("!");
            }
        }).subscribe(new Subscriber<String>() {//被訂閱
            @Override
            public void onCompleted() {
                Log.e(TAG, "onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "onError");
            }

            @Override
            public void onNext(String s) {
                Log.e(TAG, s);
            }
        });
複製程式碼

hello world.png

剛接觸到這一坨程式碼你可能會說臥槽這什麼東西,大兄弟先別忙著走,我那麼寫只是為了把RxJava鏈式呼叫的特點展現在你面前,接下來讓我們從Observeable和Observer的建立開始。

  • 建立Observable 建立Observable的方式非常的多,先介紹一下非常基本的** Create **
        Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                subscriber.onNext("Hello");
                subscriber.onNext("World");
                subscriber.onNext("!");                
            }
        })
複製程式碼

通過create()方法可以建立一個Observable物件我們是知道了,那麼create()方法中的引數是什麼呢?

    /**
     * Invoked when Observable.subscribe is called.
     */
    public interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {
        // cover for generics insanity
    }
複製程式碼

這個引數是個介面,那麼很明顯了——這個引數是用來幹回撥這事的,發射資料的時候將會用這個介面的實現類通過這個引數發射資料。而call方法則來源於其父介面Action1。call這個方法給出了一個subscriber引數,讓我們看一下這個subscriber究竟是誰。

是誰?.png

可能你會說這不廢話嗎……閉著眼我都能知道這是炮姐……繼續我們的話題,在這個類實現的介面裡我們發現了一個看起來非常熟悉的東西** Observer **是了,這個subscriber就是一個訂閱者,一個訂閱者加強版。他相對於Observer主要有兩點區別,一個onStart()會在訂閱開始,事件傳送前執行,所以你可以在這個方法裡做一些準備工作。另一點是實現的另外一個介面提供的unsubscribe(),取消訂閱。據扔物線的文章說,不取消訂閱可能會有記憶體洩露的風險,關於這一點很容易理解,非同步可能會由於生命週期長短問題引發記憶體洩漏,在這裡就不多加贅述了。

  • 建立Observer 看完了Observable的建立,我們再來看一下Observer的建立
        Observer<String> observer = new Observer<String>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(String s) {
                Log.e(TAG,s);
            }
        };
複製程式碼

按照老樣子,點進Observer發現這個也是個介面,那麼我們在使用多型建立這個方法的時候必須要實現他的三個方法。

  • 訂閱
observable.subscribe(observer);
複製程式碼

上面的程式碼看起來像是observable訂閱了observer,但事實上這種設計是出於流式api的考慮,什麼是流式api?看看我的hello world例項程式碼是怎麼寫的,那就是流式api設計的好處。整個程式碼看起來像是一個鏈子,優雅而簡潔。

好了,最基本的介紹完了,你現在可以去嘗試一下你的Hello World了。不過在嘗試之前,我需要糾正我上述Hello World示例程式碼的一個錯誤:RxJava文件中對於Observable的描述有這麼一段話,一個形式正確的Observable必須嘗試呼叫一次onCompleted或者呼叫一次onError方法。很明顯,我的demo是一個使用方法錯誤的例子。此處對Observable和Observer的api介紹非常的少,因為我覺得一次性把文件上的方法全給你搬上來並不明智,一是用不上那麼多,二是容易混淆。

執行緒控制——Scheduler###

終於要到重點了,執行緒控制絕對是RxJava的重點之一。在不指定執行緒的情況下,RxJava遵循的是執行緒不變的原則,在哪個執行緒呼叫subscribe(),就在哪個執行緒生產、消費事件。這對於大部分開發人員來說都是難以接受的事,因為如果是耗時操作可能會阻塞當前執行緒,這是開發者不想看到的,好在我們是可以切換執行緒的。下面同樣是摘自的描述:

  • Schedulers.computation( ):用於計算任務,如事件迴圈或回撥處理,不要用於IO操作,預設執行緒數等於處理器的數量。

  • Schedulers.from(executor):使用指定的Executor作為排程器

  • Schedulers.immediate( ):在當前執行緒立即開始執行任務

  • Schedulers.io( ):用於IO密集型任務,如非同步阻塞IO操作,這個排程器的執行緒池會根據需要增長;Schedulers.io()預設是一個CachedThreadScheduler,很像一個有執行緒快取的新執行緒排程器。

  • Schedulers.newThread( ):為每個任務建立一個新執行緒

  • Schedulers.trampoline( ):當其它排隊的任務完成後,在當前執行緒排隊開始執行

這裡只是初步的瞭解一下,畢竟本文定位是一篇基礎級的文,以下給出一個簡單的載入圖片的例子。

簡單的例子

首先明確我們要乾的事:通過一個url載入一張圖,恩為了演示RxJava和執行緒控制,我用HttpUrlConnection來做一個例項。

        Observable.create(new Observable.OnSubscribe<Bitmap>() {
            @Override
            public void call(Subscriber<? super Bitmap> subscriber) {
                try {
                    URL url = new URL("http://img4.imgtn.bdimg.com/it/u=815679381,647288773&fm=21&gp=0.jpg");
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);

                    InputStream in = connection.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(in);
                    subscriber.onNext(bitmap);
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        })
                .observeOn(AndroidSchedulers.mainThread())  //指定subscriber的回撥發生在UI執行緒
                .subscribeOn(Schedulers.newThread())        //指定subscribe()發生在新執行緒
                .subscribe(new Subscriber<Bitmap>() {
                    @Override
                    public void onCompleted() {
                        plan.setVisibility(View.GONE);
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(Bitmap bitmap) {
                        if (bitmap != null) {
                            image.setImageBitmap(bitmap);
                            onCompleted();
                        }
                    }
                });
    }
複製程式碼

炮姐.gif

上面的程式碼寫的很清楚了,我是通過observeOn(AndroidSchedulers.mainThread())指定訂閱者的回撥發生在主執行緒,因為這裡給ImageView設定圖片需要在主執行緒進行,通過subscribeOn(Schedulers.newThread())指定subscribe()發生在新執行緒。

最後請忽略最後幾秒的蜜汁小圓點,因為我不摸螢幕AndroidStudio的錄製就會停留在載入出圖片後的那一段時間,錄製出來的效果非常差。我載入的這張圖是非常小的,我通過限制wifi網速為5k/s來達到“載入”這個目的。

一些反思

本文說的並不深入,只是一篇基礎,看完了這篇可能你只能寫兩個小demo。但是就如我上文所說的,我認為學一個東西,基礎是十分重要的,只要你梳理清楚基礎和關鍵,學習起來無疑是事半功倍的。

我在文章最開頭寫的demo我為什麼要說這很不“RxJava”?因為我只是傳遞了兩個Integer型別的數,之後通過map操作符將這兩個轉換為String,在訂閱的回撥裡處理這兩個字元,並執行相應的邏輯。這給我的感覺就和以前寫程式碼的感覺差不多,沒有一種鏈式呼叫的爽快感,反而有一種強行用RxJava的感覺。那麼RxJava的應用場景和操作符究竟有什麼玄機?我會繼續探索,繼續分享。請期待~

推薦資料

給Android開發者的RxJava詳解:http://gank.io/post/560e15be2dca930e00da1083#toc_14

ReactiveX/RxJava文件中文版:https://mcxiaoke.gitbooks.io/rxdocs/content/

大頭鬼深入淺出RxJava系列:http://blog.csdn.net/lzyzsd/article/details/44094895

iamxiarui探索RxJava系列,也是我比較推薦的入門文,我這同學總結和配圖都是一流的:http://www.jianshu.com/p/856297523728

相關文章