互聯網應用開發中因爲請求網絡數據頻繁,每每後面一個請求的參數是前面一個請求的結果,因而常常須要在前面一個請求的響應中去發送第二個請求,從而形成「請求嵌套」的問題。若是層次比較多,代碼可讀性和效率都是問題。本文首先從感性上介紹下RxJava,而後講解如何經過RxJava中的flatMap操做符來處理「嵌套請求」的問題java
這裏並不打算詳細介紹RxJava的用法和原理,這方面的文章已經不少了。這裏僅僅闡述本人對於RxJava的感性上的理解。先上一個圖:
咱們都知道RxJava是基於觀察者模式的,可是和傳統的觀察者模式又有很大的區別。傳統的觀察者模式更注重訂閱和發佈這個動做,而RxJava的重點在於數據的「流動」。
若是咱們把RxJava中的Observable看作一個盒子,那麼Observable就是把數據或者事件給裝進了這個易於拿取的盒子裏面,讓訂閱者(或者下一級別的盒子)能夠拿到而處理。這樣一來,原來靜態的數據/事件就被流動起來了。編程
咱們知道人類會在河流中建設大壩,其實咱們能夠把RxJava中的filter/map/merge等Oberservable操做符看作成數據流中的大壩,通過這個操做符的操做後,大壩數據流被過濾被合併被處理,從而靈活的對數據的流動進行管制,讓最終的使用者靈活的拿到。json
以上就是我對RxJava的理解,深刻的用法和原理你們請自行看網上的文章。api
這裏開始進入正題,開始舉一個嵌套請求的例子。
好比咱們下列接口:數組
咱們最終的目的是要打印班上全部同窗分別所上的課程(大學,同班級每一個學生選上的課不同),按照傳統Volley的作法,代碼大概是這樣子(Volley已經被封裝過)網絡
private void getAllStudents(String id) { BaseRequest baseRequest = new BaseRequest(); baseRequest.setClassId(id); String url = AppConfig.SERVER_URL + "api/students/getAll"; final GsonRequest request = new GsonRequest<>(url, baseRequest, Response.class, new Response.Listener<Response>() { @Override public void onResponse(Response response) { if (response.getStatus() > 0) { List<Student> studentList = response.getData(); for (Student student : studentList) { } } else { //error } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { //error } }); MyVolley.startRequest(request); } private void getAllCourses(String id) { BaseRequest baseRequest = new BaseRequest(); baseRequest.setStudentId(id); String url = AppConfig.SERVER_URL + "api/courses/getAll"; final GsonRequest request = new GsonRequest<>(url, baseRequest, Response.class, new Response.Listener<Response>() { @Override public void onResponse(Response response) { if (response.getStatus() > 0) { List<Course> courseList = response.getData(); for (Course course : courseList) { //use } } else { //error } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { //error } }); MyVolley.startRequest(request); }
顯然第一個請求的響應中得到的數據是一個List,正對每個List中的item要再次發送第二個請求,在第二個請求中得到最終的結果。這就是一個嵌套請求。這會有兩個問題:閉包
如今咱們能夠放出RxJava大法了,flatMap是一個Observable的操做符,接受一個Func1閉包,這個閉包的第一個函數是待操做的上一個數據流中的數據類型,第二個是這個flatMap操做完成後返回的數據類型的被封裝的Observable。說白了就是講一個多級數列「拍扁」成了一個一級數列。
按照上面的列子,flatMap將接受student後而後獲取course的這個二維過程給線性化了,變成了一個可觀測的連續的數據流。
因而代碼是:app
ConnectionBase.getApiService2() .getStudents(101) .flatMap(new Func1<Student, Observable<Course>>() { @Override public Observable<Course> call(Student student) { return ConnectionBase.getApiService2().getAllCourse(student.getId()); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<Course>() { @Override public void call(Course course) { //use the Course } });
是否是代碼簡潔的讓你看不懂了?別急,這裏面的getStutent和getAllCourse是ConnectionBase.getApiService2()的兩個方法,他集成了Retrofit2用來將請求的網絡數據轉化成Observable,最後一節將介紹,這裏先不關注。
咱們所要關注的是以上代碼的流程。
首先getStudent傳入了班級id(101)返回了Observable
flatMap的做用就是對傳入的對象進行處理,返回下一級所要的對象的Observable包裝。異步
FuncX和ActionX的區別。FuncX包裝的是有返回值的方法,用於Observable的變換、組合等等;ActionX用於包裝無返回值的方法,用於subscribe方法的閉包參數。Func1有兩個入參,前者是原始的參數類型,後者是返回值類型;而Action1只有一個入參,就是傳入的被消費的數據類型。
subscribeOn(Schedulers.io()).observeOn(AndroidScheduler.mainThread())是最經常使用的方式,後臺讀取數據,主線程更新界面。subScribeOn指在哪一個線程發射數據,observeOn是指在哪裏消費數據。因爲最終的Course要刷新界面,必需要在主線程上調用更新view的方法,因此observeOn(AndroidScheduler.mainThread())是相當重要的。
運用flatMap的地方也是能夠用map的,可是是有區別的。先看下map操做符的用法:
ConnectionBase.getApiService2() .getStudents(101) .map(new Func1<Student>, Course>() { @Override public Course call(Student student) { return conventStudentToCourse();// has problem } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<Course>() { @Override public void call(Course course) { //use the Course } });
能夠看到map也是接受一個Func1閉包,可是這個閉包的第二個參數即返回值參數類型並非一個被包裝的Observable,而是實際的原始類型,因爲call的返回值是Course,因此conventStudentToCourse這裏就不能用Retrofit2的方式返回一個Observable了。
因此這裏是有一個問題的,對於這種嵌套的網絡請求,因爲接到上端數據流處處理後將結果數據放入下端數據流是一個異步的過程,而conventStudentToCourse這種直接將Student轉化爲Course是無法作到異步的,由於沒有回調方法。那麼這種狀況,最好仍是用flatMap並經過retrofit的方式來獲取Observable。要知道,Rxjava的一個精髓就是「異步」。
那麼到底map和flatMap有什麼區別,或者說何時使用map何時使用flatMap呢?
flatMap() 和 map() 有一個相同點:它也是把傳入的參數轉化以後返回另外一個對象。但須要注意,和 map() 不一樣的是, flatMap() 中返回的是個 Observable 對象,而且這個 Observable 對象並非被直接發送到了 Subscriber 的回調方法中。
首先,若是你須要將一個類型的對象通過處理(非異步)直接轉化成下一個類型,推薦用map,不然的話就用flatMap。
其次,若是你須要在處理中加入容錯的機制(特別是你本身封裝基於RxJava的網絡請求框架),推薦用flatMap。
好比將一個File[] jsonFile中每一個File轉換成String,用map的話代碼爲:
Observable.from(jsonFile).map(new Func1<File, String>() { @Override public String call(File file) { try { return new Gson().toJson(new FileReader(file), Object.class); } catch (FileNotFoundException e) { // So Exception. What to do ? } return null; // Not good :( } });
能夠看到這裏在出現錯誤的時候直接拋出異常,這樣的處理其實並很差,特別若是你本身封裝框架,這個異常不大好去抓取。
若是用flatMap,因爲flatMap的閉包返回值是一個Observable,因此咱們能夠在這個閉包的call中經過Observable.create的方式來建立Observable,而要知道create方法是能夠控制數據流下端的Subscriber的,便可以調用onNext/onCompete/onError方法。若是出現異常,咱們直接調用subscribe.onError便可,封裝框架也很好感知。代碼大體以下:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() { @Override public Observable<String> call(final File file) { return Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { try { String json = new Gson().toJson(new FileReader(file), Object.class); subscriber.onNext(json); subscriber.onCompleted(); } catch (FileNotFoundException e) { subscriber.onError(e); } } }); } });
這裏該討論Retrofit了。能夠說Retrofit就是爲了RxJava而生的。若是你的項目以前在網絡請求框架用的是Volley或者本身封裝Http請求和TCP/IP,而如今你看到了Retrofit這個框架後想使用起來,我能夠負責任的跟你說,若是你的項目中沒有使用RxJava的話,使用Retrofit和Volley是沒有區別的!要用Retrofit的話,就最好或者說強烈建議也使用RxJava進行編程。
Retrofit有callback和Observable兩種模式,前者就像傳統的Volley同樣,有successs和fail的回調方法,咱們在success回調方法中處理結果;而Observable模式是將請求回來的數據由Retrofit框架自動的幫你加了一個盒子,即自動幫你裝配成了含有這個數據的Observable,供你使用RxJava的操做符隨意靈活的進行變換。
callback模式的Retrofit是這樣創建的:
retrofit = new Retrofit.Builder() .baseUrl(SERVER_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build();
Observable模式是這樣子創建的:
retrofit2 = new Retrofit.Builder() .baseUrl(SERVER_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build();
即addCallAdapterFactory這個方法在起做用,在RxJavaCallAdapterFactory的源碼註釋中能夠看到這麼一句話:
Response wrapped body (e.g., {@code Observable<Response
>}) calls {@code onNext} with a {@link Response} object for all HTTP responses and calls {@code onError} with {@link IOException} for network errors
即它將返回值body爲包裹上了一層「Observable」