Android拾萃 - 從零打造一個RxJava(搞清楚RxJava代碼為什么這么寫)

任何框架都是從無到有,都是為了解決問題而產生,那么RxJava是如何產生的呢?RxJava代碼的寫法,為何如此讓人看不懂,回調的參數等等,讓小白看了摸不著頭腦。

接下來的文章,主要是依據NotRxJava懶人專用指南,結合自己的理解,寫的一篇更加小白的文章,以幫助我們更好的梳理和理解。

Cat 應用程序

讓我們來創建一個真實世界的例子。我們都知道貓是我們技術發展的引擎,所以就讓我們也來創建這么一個用來下載貓圖片的典型應用吧。

任務描述

我們有個 Web API,能根據給定的查詢請求搜索到整個互聯網上貓的圖片。每個圖片包含可愛指數的參數(描述圖片可愛度的整型值)。我們的任務將會下載到一個貓列表的集合,選擇最可愛的那個,然后把它保存到本地。
我們只關心下載、處理和保存貓的數據。

我們開始吧~

思維導圖

這里各個點后面都會結合這個圖,進行解說

貓程序.png
模型和 API

根據我們的任務,我們能夠得到的信息

  1. 給定的查詢信息String query
  2. 搜索所有的貓圖片 List<Cat> queryCats(String query)
  3. 每個圖片包含可愛指數的參數(描述圖片可愛度的整型值)找到最可愛的貓 (Cat 包含圖片Bitmap image和可愛屬性int cuteness)。
  4. 最可愛的貓圖片保存到本地(Uri store(Cat cat),不清楚Uri 的自行查閱)

“貓”的數據結構:

public class Cat implements Comparable<Cat> {
    //圖片
    Bitmap image;
    //可愛屬性
    int cuteness;

    //為了實現比較功能,我們的Cat對象需要實現Comparable<Cat>接口,然后重寫compareTo方法
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public int compareTo(@NonNull Cat another) {
        //這里大小比較只針對可愛屬性  cuteness
        return Integer.compare(cuteness, another.cuteness);
    }
}

我們先不考慮異步情況,那么我們的接口Api:

public interface Api {
    //阻塞
    List<Cat> queryCats(String query);
    Uri store(Cat cat);
}

然后吧我們的業務邏輯封裝到我們的helper類


public class CatsHelper {
    Api api;

    //組合方法
    public Uri saveTheCutestCat(String query){
        List<Cat> cats = api.queryCats(query);
        Cat cat = findCutestCat(cats);
        Uri uri = api.store(cat);
        return uri;
    }

    private Cat findCutestCat(List<Cat> cats) {
        return Collections.max(cats);
    }
}

我們知道,在實際應用之中,我們會處理很多任務,如果使用上面的阻塞方式,用戶的每次操作都需要等待返回之后才能繼續,這明顯是不好的,因此我們需要異步

異步Api接口

我們queryCats是一個http請求,搜索完畢,需要有一個回調告訴我們,以讓我們處理數據,并進行下步業務邏輯,于是,新的api的代碼如下:

public interface Api {
    //阻塞api
    List<Cat> queryCats(String query);
    Uri store(Cat cat);

    //異步api,這里就不需要返回值了,直接在回調里獲取即可
    void queryCatsAsync(String query, CatsQueryCallback callback);
    void store(Cat cat, StoreCallback storeCallback);

//請求的接口回調
    interface CatsQueryCallback {
        void onCatListReceived(List<Cat> cats);
        void onError(Exception e);
    }

//保存數據的接口回調
    interface StoreCallback{
        void onCatStored(Uri uri);
        void onStoreFailed(Exception e);
    }
}

我們的helper類,作出相應的更改,為了讓異步更加完整,我們比較最可愛的貓成功或者失敗,都需要告訴用戶,于是也需要寫一個接口CutestCatCallback回調出去。


public class CatsHelper {

    private Cat findCutestCat(List<Cat> cats) {
        return Collections.max(cats);
    }

    //監聽接口
    public interface CutestCatCallback {
        void onCutestCatSaved(Uri uri);
        void onQueryFailed(Exception e);
    }

    Api api;

    //組合方法
    public Uri saveTheCutestCat(String query){
        List<Cat> cats = api.queryCats(query);
        Cat cat = findCutestCat(cats);
        Uri uri = api.store(cat);
        return uri;findCutestCat
    }

    //異步
    public  void saveTheCutestCatAsyc(final String query, final CutestCatCallback cutestCatCallback){
        api.queryCatsAsync(query, new Api.CatsQueryCallback() {
            @Override
            public void onCatListReceived(List<Cat> cats) {
                Cat cat = findCutestCat(cats);
                api.store(cat, new Api.StoreCallback() {
                    @Override
                    public void onCatStored(Uri uri) {
                        cutestCatCallback.onCutestCatSaved(uri);
                    }

                    @Override
                    public void onStoreFailed(Exception e) {
                        cutestCatCallback.onQueryFailed(e);
                    }
                });

            }

            @Override
            public void onError(Exception e) {
                cutestCatCallback.onQueryFailed(e);
            }
        });
    }

}

現在它有了更多無關代碼和花括號,但是邏輯是一樣的。
每一個異步操作,我們都必須創建出回調接口并在代碼中手動的插入它們,接口也越來越多!
在這樣的代碼中錯誤不會自動地傳遞,我們需要在手動在onStoreFailed和onQueryFailed方法里面,添加代碼,將錯誤傳遞給上一層。

為了解決這些問題,我們引入了泛型回調

泛型回調
泛型類
/**
 * 定義泛型類
 *
 * 與普通類的定義相比,上面的代碼在類名后面多出了 <T1, T2>,
 * T1, T2 是自定義的標識符,也是參數,用來傳遞數據的類型,而不是數據的值,我們稱之為類型參數。
 * 在泛型中,不但數據的值可以通過參數傳遞,數據的類型也可以通過參數傳遞。
 * T1, T2 只是數據類型的占位符,運行時會被替換為真正的數據類型。
 * Created by philos on 17-9-16.
 */
public class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}
泛型接口
/**
 * 定義泛型接口
 * Created by philos on 17-9-16.
 */

interface Info<T> {
    public T getVar();
}

接口實現類


/**
 * 實現泛型接口
 * Created by philos on 17-9-16.
 */

public class InfoImp<T> implements Info<T> {

    private T var;

    // 定義泛型構造方法
    public InfoImp(T var) {
        this.setVar(var);
    }
    public void setVar(T var) {
        this.var = var;
    }

    @Override
    public T getVar() {
        return this.var;
    }
}
泛型方法(固定參數 和 可變參數)
public class Main {
    //泛型方法
    public static <T> void out(T t) {
        System.out.println(t);
    }

    //可變參數 泛型方法
    public static <T> void out(T... args) {
        for (T t : args) {
            System.out.println(t);
        }
    }

    public static void main(String[] args) {
        //普通泛型
        out("findingsea");
        out(123);
        out(11.11);
        out(true);

        //可變參數泛型
        out("findingsea", 123, 11.11, true);
    }
}

了解了上面具體的寫法,我們繼續改造我們的貓程序代碼,請看上面的思維導圖左邊的異步優化。

泛型回調

對比接口可以發現,回調情況有兩種,成功和失敗,成功的返回一個對象,失敗的返回Exception類,于是新增統一的回調接口如下:

/**
 * 泛型回調
 * 找到共同的模式,進行抽離
 * 替代原來所有的接口
 * Created by philos on 17-9-15.
 */

public interface Callback<T> {
    void onResult(T result);
    void onError(Exception e);
}

Api 包裝類ApiWrapper

我們的之前就定義完畢了,上線之后,一般不會進行大幅度修改,這個時候,我們可以實現一個Api包裝類。

/**
 * 使用了泛型之后,兩個接口合并為一個接口
 * Created by philos on 17-9-15.
 */

public class ApiWrapper {
    Api api;

    public void queryCats(String query, final Callback<List<Cat>> castCallback){
        api.queryCatsAsync(query, new Api.CatsQueryCallback() {
            @Override
            public void onCatListReceived(List<Cat> cats) {
                castCallback.onResult(cats);
            }

            @Override
            public void onError(Exception e) {
                castCallback.onError(e);
            }
        });
    }

    public void store(Cat cat, final Callback<Uri> uriCallback){
        api.store(cat, new Api.StoreCallback() {
            @Override
            public void onCatStored(Uri uri) {
                uriCallback.onResult(uri);
            }

            @Override
            public void onStoreFailed(Exception e) {
                uriCallback.onError(e);
            }
        });
    }
}

我們對應的helper類就不在持有Api對象了,換成了包裝類,代碼修改如下:

public class CatsHelper {
    private Cat findCutestCat(List<Cat> cats) {
        return Collections.max(cats);
    }

    //監聽接口
    public interface CutestCatCallback {
        void onCutestCatSaved(Uri uri);
        void onQueryFailed(Exception e);
    }

    //泛型回調 (retrofit很像有木有)
    ApiWrapper apiWrapper;

    public  void saveTheCutestCatWrap(final String query, final CutestCatCallback cutestCatCallback){
        apiWrapper.queryCats(query, new Callback<List<Cat>>() {
            @Override
            public void onResult(List<Cat> result) {
                Cat cat = findCutestCat(result);
                apiWrapper.store(cat, new Callback<Uri>() {
                    @Override
                    public void onResult(Uri result) {
                        cutestCatCallback.onCutestCatSaved(result);
                    }

                    @Override
                    public void onError(Exception e) {
                        cutestCatCallback.onQueryFailed(e);
                    }
                });
            }

            @Override
            public void onError(Exception e) {
                cutestCatCallback.onQueryFailed(e);
            }
        });
    }

}

分解,使用臨時泛型對象

我們發現異步操作(queryCats,queryCats,還有saveTheCutestCat),它們都遵循了相同的模式。調用它們的方法有一些參數(query、cat)也包括一個回調對象。再次說明:任何異步操作需要攜帶所需的常規參數和一個回調實例對象。請看思維導圖左邊異步優化。

臨時泛型對象

對外提供的方法名暫時起為start

/**
 * 分解異步操作,每步有一個參數
 * 每步返回,臨時對象
 * 每步攜帶 回調信息
 * 避免回調地獄,用鏈式解決問題
 * Created by philos on 17-9-16.
 */

public abstract class AsyncJob<T> {
    //包裝了方法,對外隱藏,外部只需傳入回調,定義返回類型,調用是start即可
    public abstract void start(Callback<T> callback);
}

改造wrapper類,把我們以前的方法也隱藏起來了


/**
 * 所有的異步操作都統一了
 * 每次異步操作都返回AsyncJob<T>
 * 每次異步都攜帶 回調Callback<T>
 * start包裝了真正的請求,外部不關心,只要調用start即可
 * Created by philos on 17-9-16.
 */
public class ApiWrapper {
    Api api;

    public AsyncJob<List<Cat>> queryCats(final String query){
        //返回臨時對象
        return new AsyncJob<List<Cat>>() {
            @Override
            public void start(final Callback<List<Cat>> callback) {
                //這里進行請求,然后返回給callback就可以了
                api.queryCatsAsync(query, new Api.CatsQueryCallback() {
                    @Override
                    public void onCatListReceived(List<Cat> cats) {
                        callback.onResult(cats);
                    }

                    @Override
                    public void onError(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };
    }


    public AsyncJob<Uri> store(final Cat cat){
        return new AsyncJob<Uri>() {
            @Override
            public void start(final Callback<Uri> callback) {
                //進行請求,然后返回給callback
                api.store(cat, new Api.StoreCallback() {
                    @Override
                    public void onCatStored(Uri uri) {
                        callback.onResult(uri);
                    }

                    @Override
                    public void onStoreFailed(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };
    }
}

helper類也需要進行修改

public class CatsHelper {

    private Cat findCutestCat(List<Cat> cats) {
        return Collections.max(cats);
    }

    //監聽接口
    public interface CutestCatCallback {
        void onCutestCatSaved(Uri uri);
        void onQueryFailed(Exception e);
    }
    //分解統一返回臨時對象,鏈式調用,高大上有木有

    ApiWrapper apiWrapper;

    public AsyncJob<Uri> saveTheCutestCat(final String query){
        //直接new 需要返回的對象
        return new AsyncJob<Uri>() {
            @Override
            public void start(final Callback<Uri> callback) {
                //這里請求數據,返回,  每個異步都有自己的回調信息這里重新new 一個CallBack
                apiWrapper.queryCats(query).start(new Callback<List<Cat>>() {
                    @Override
                    public void onResult(List<Cat> result) {
                        //進行數據操作和轉換
                        Cat cat = findCutestCat(result);
                        apiWrapper.store(cat).start(new Callback<Uri>() {
                            @Override
                            public void onResult(Uri result) {
                                callback.onResult(result);
                            }

                            @Override
                            public void onError(Exception e) {
                                callback.onError(e);
                            }
                        });
                    }

                    @Override
                    public void onError(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };
    }
}

是我們的邏輯數據流:

        (async)                 (sync)           (async)
query ===========> List<Cat> -------------> Cat ==========> Uri
queryCats              findCutest           store

分解成更小的操作


public class CatsHelper {

    private Cat findCutestCat(List<Cat> cats) {
        return Collections.max(cats);
    }

    //監聽接口
    public interface CutestCatCallback {
        void onCutestCatSaved(Uri uri);
        void onQueryFailed(Exception e);
    }

    ApiWrapper apiWrapper;

    public AsyncJob<Uri> saveTheCutestCat(String query) {
        //獲取貓列表  返回臨時對象  catsListAsyncJob
        final AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper.queryCats(query);

        //查找最可愛貓  返回臨時對象  cutestCatAsyncJob
        final AsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() {
            @Override
            public void start(final Callback<Cat> callback) {
                catsListAsyncJob.start(new Callback<List<Cat>>() {
                    @Override
                    public void onResult(List<Cat> result) {
                        callback.onResult(findCutestCat(result));
                    }

                    @Override
                    public void onError(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };

        //保存貓到本地  返回臨時對象  storedUriAsyncJob
        AsyncJob<Uri> storedUriAsyncJob = new AsyncJob<Uri>() {
            @Override
            public void start(final Callback<Uri> cutestCatCallback) {
                cutestCatAsyncJob.start(new Callback<Cat>() {
                    @Override
                    public void onResult(Cat cutest) {
                        apiWrapper.store(cutest)
                                .start(new Callback<Uri>() {
                                    @Override
                                    public void onResult(Uri result) {
                                        cutestCatCallback.onResult(result);
                                    }

                                    @Override
                                    public void onError(Exception e) {
                                        cutestCatCallback.onError(e);
                                    }
                                });
                    }

                    @Override
                    public void onError(Exception e) {
                        cutestCatCallback.onError(e);
                    }
                });
            }
        };
        return storedUriAsyncJob;
    }

映射 (其實就是操作符的封裝)

現在看看 AsyncJob<Cat> cutestCatAsyncJob的部分:

AsyncJob<Cat> cutestCatAsyncJob = new AsyncJob<Cat>() {
            @Override
            public void start(Callback<Cat> callback) {
                catsListAsyncJob.start(new Callback<List<Cat>>() {
                    @Override
                    public void onResult(List<Cat> result) {
                        callback.onResult(findCutest(result));
                    }
 
                    @Override
                    public void onError(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };

這 16 行代碼只有一行是對我們有用(對于邏輯來說)的操作:

findCutest(result)

剩下的僅僅是開啟另外一個AsyncJob和傳遞結果與錯誤的樣板代碼。此外,這些代碼并不用于特定的任務,我們可以把其移動到其它地方而不影響編寫我們真正需要的業務代碼。

我們要怎么做呢?

將方法findCutest分離出來,并和前面一樣返回臨時對象,供下步使用。

在 Java 中不能直接傳遞方法(函數)所以我們需要通過類(和接口)來間接實現這樣的功能,我們來定義轉換方法的接口:

轉換方法的接口
/**
 * 方法傳入接口
 * T對應于參數類型而R對應于返回類型
 * Created by philos on 17-9-16.
 */

public interface Func<T, R> {
    R call(T t);
}

我們之前返回的臨時對象的start方法是通過回調返回的,但是現在是調用方法轉換需要返回臨時對象,接著上面繼續看怎么改造。
我們新增一個方法,就叫map,返回對象R,參考上面泛型方法,AsyncJob代碼修改如下:

public abstract class AsyncJob<T> {
    //包裝了方法,對外隱藏,外部只需傳入回調,定義返回類型,調用是start即可
    public abstract void start(Callback<T> callback);
//返回臨時對象 方法(需要傳入映射的方法Func<T, R> func攜帶的是對象R),
    public <R> AsyncJob<R> map(final Func<T, R> func){
        //使用自己,為鏈式調用返回同樣的對象(return 一個新建的)
        final AsyncJob<T> source = this;
        return new AsyncJob<R>() {
            @Override
            public void start(final Callback<R> callback) {
                source.start(new Callback<T>() {
                    @Override
                    public void onResult(T result) {
                        //方法 替換成func
                        //返回對象為R
                        R mapped = func.call(result);
                        callback.onResult(mapped);
                    }

                    @Override
                    public void onError(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };
    }
}

前面map對應的是分離一個方法返回的是實體對象的情況,那么如果是需要返回同樣的臨時對象的方法,比如上面的CatsHelper 的storedUriAsyncJob嵌套了兩個start方法,所以AsyncJob在添加一個方法,就叫做flatMap


    //返回臨時對象 方法(需要傳入映射的方法Func<T, AsyncJob<R>> func攜帶的是上一個臨時對象AsyncJob<R>)
    public <R> AsyncJob<R> flatMap(final Func<T, AsyncJob<R>> func){
        final AsyncJob<T> source = this;
        return new AsyncJob<R>() {
            @Override
            public void start(final Callback<R> callback) {
                source.start(new Callback<T>() {
                    @Override
                    public void onResult(final T result) {
                        //傳入的方法不一樣,返回值不一樣,在這里有區別
                        AsyncJob<R> mapped = func.call(result);
                        mapped.start(new Callback<R>() {
                            @Override
                            public void onResult(R result) {
                                callback.onResult(result);
                            }

                            @Override
                            public void onError(Exception e) {
                                callback.onError(e);
                            }
                        });
                    }

                    @Override
                    public void onError(Exception e) {
                        callback.onError(e);
                    }
                });
            }
        };
    }

這里我們可以簡單的理解,我們之前把回調用start表示,但是對于start還調用了其他方法的,還是存在嵌套,那么我們的map就是為了解決這個問題的。但是如果是多次start的嵌套,map明顯不滿足,這個時候我們使用flatMap 。

于是,我們的helper代碼修改如下:


public class CatsHelper {

    private Cat findCutestCat(List<Cat> cats) {
        return Collections.max(cats);
    }

    //監聽接口
    public interface CutestCatCallback {
        void onCutestCatSaved(Uri uri);
        void onQueryFailed(Exception e);
    }
  //映射
    public AsyncJob<Uri> saveTheCutestCat4(String query){
        //獲取貓列表  返回臨時對象  catsListAsyncJob
        final AsyncJob<List<Cat>> catsListAsyncJob = apiWrapper2.queryCats(query);

        //查找最可愛貓  返回臨時對象  cutestCatAsyncJob
        //這里用Func將方法findCutestCat(result)分離出來,并返回一個臨時對象cutestCatAsyncJob
        final AsyncJob<Cat> cutestCatAsyncJob = catsListAsyncJob.map(new Func<List<Cat>, Cat>() {
            @Override
            public Cat call(List<Cat> cats) {
                return findCutestCat(cats);
            }
        });

        //保存貓到本地  返回臨時對象  storedUriAsyncJob
        AsyncJob<AsyncJob<Uri>> storedUriAsyncJob = cutestCatAsyncJob.map(new Func<Cat, AsyncJob<Uri>>() {
            @Override
            public AsyncJob<Uri> call(Cat cat) {
                return apiWrapper2.store(cat);
            }
        });
        return storedUriAsyncJob;
    }
}

RxJava

嘿,你不需要把那些代碼拷到你的項目中,因為我們還是實現地不夠完全的,僅僅算是非線程安全的 RxJava 的一小部分而已。
它們之間只有一些差異:
AsyncJob<T>
就是實際上的 Observable
,它不僅可以只分發一個單一的結果也可以是一個序列(可以為空)。

Callback<T>
就是 Observer
,除了 Callback 少了onNext(T t)
方法。Observer 中在onError(Throwable t)
方法被調用后,會繼而調用onCompleted()
,然后 Observer 會包裝好并發送出事件流(因為它能發送一個序列)。

abstract void start(Callback<T> callback)
對應 Subscription subscribe(final Observer<? super T> observer),這個方法也返回 Subscription ,在不需要它時你可以決定取消接收事件流。

除了map
和flatMap
方法,Observable
在 Observalbes 之上也有一些其它有用的操作。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一、簡歷準備 1、個人技能 (1)自定義控件、UI設計、常用動畫特效 自定義控件 ①為什么要自定義控件? Andr...
    lucas777閱讀 5,244評論 2 54
  • RxJava詳解(二) 說好的簡潔呢? 上面這一部分,又是介紹、又是Hello World、又是數據變換,但是你會...
    CharonChui閱讀 332評論 0 0
  • 6月,桃子的季節。每到這個時候,我就想起了奶奶,還有奶奶門前那顆桃子樹。 桃子樹不高,卻有非常多的枝丫。春天的時候...
    老衲兮閱讀 212評論 0 0
  • https://www.shaketheskycasino.com/mobile/index.php
    必應2016閱讀 360評論 0 0
  • 生命的起點與終點,僅僅源于瞬間,人若浮塵游走于天地間,看盡世間萬象,歷盡塵世繁俗,驀然回首:人不在,心在;心不在,...
    斜陽脈脈水悠悠閱讀 136評論 0 3