RxJava 與 Retrofit 結合的最佳實踐
前言
RxJava和Retrofit也火了一段時間了,不過最近一直在學習ReactNative和Node相關的姿勢,一直沒有時間研究這些新東西,最近有個專案準備寫,打算先用Android寫一個Demo出來,卻發現Android的世界發生了天翻地覆的變化,EventBus和OKHttp啥的都不見了,RxJava和Retrofit是什麼鬼?
好吧,到Github上耐著性子看過了RxJava和Retrofit的介紹和幾個Demo,原來Android的大神Jake Wharton為Retrofit這個專案貢獻了這麼多的程式碼,沒有道理不用了。
如果你對RxJava不熟悉請先看給 Android 開發者的 RxJava 詳解這篇文章。
如果你對Retrofit不熟悉就先看Retrofit官網。
當然也有很多RxJava與Retrofit的文章,但是我覺得很多大家都很糾結的功能都沒有被總結出來,所以才有了此篇文章。
歡迎大家拍磚。
接下來進入正文,我是從下面幾個角度去思考RxJava與Retrofit結合的。
- RxJava如何與Retrofit結合
- 相同格式的Http請求資料該如何封裝
- 相同格式的Http請求資料統一進行預處理
- 如何取消一個Http請求 -- 觀察者之間的對決,Oberver VS Subscriber
- 一個需要ProgressDialog的Subscriber該有的樣子
1.RxJava如何與Retrofit結合
1.1 基本頁面
先扔出build.gradle
檔案的內容
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.2.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.jakewharton:butterknife:7.0.1'
}
也就是說本文是基於RxJava1.1.0和Retrofit 2.0.0-beta4來進行的。 新增rxandroid是因為rxjava中的執行緒問題。
下面先搭建一個基本的頁面,頁面很簡單,先來看檔案目錄結構
activity_main.xml的程式碼如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".activity.MainActivity">
<Button
android:id="@+id/click_me_BN"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="5dp"
android:text="點我"
android:textSize="16sp"/>
<TextView
android:id="@+id/result_TV"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/click_me_BN"
android:text="Hello World!"
android:textSize="16sp"/>
</RelativeLayout>
MainActivity.java的程式碼如下:
package com.queen.rxjavaretrofitdemo.activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.TextView;
import com.queen.rxjavaretrofitdemo.R;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
@Bind(R.id.click_me_BN)
Button clickMeBN;
@Bind(R.id.result_TV)
TextView resultTV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.click_me_BN)
public void onClick() {
getMovie();
}
//進行網路請求
private void getMovie(){
}
}
注意不要忘記加網路許可權
<uses-permission android:name="android.permission.INTERNET"/>
1.2 只用Retrofit
我們準備在getMovie方法中進行網路請求,我們先來看看只使用Retrofit是如何進行的。
我們使用豆瓣電影的Top250做測試連線,目標地址為
https://api.douban.com/v2/movie/top250?start=0&count=10
至於返回的資料格式,大家自己訪問下連結就看到了,太長就不放進來了。
首先我們要根據返回的結果封裝一個Entity,暫命名為MovieEntity,程式碼就不貼了。
接下來我們要建立一個介面取名為MovieService,程式碼如下:
public interface MovieService {
@GET("top250")
Call<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}
回到MainActivity之中,我們來寫getMovie方法的程式碼
//進行網路請求
private void getMovie(){
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
MovieService movieService = retrofit.create(MovieService.class);
Call<MovieEntity> call = movieService.getTopMovie(0, 10);
call.enqueue(new Callback<MovieEntity>() {
@Override
public void onResponse(Call<MovieEntity> call, Response<MovieEntity> response) {
resultTV.setText(response.body().toString());
}
@Override
public void onFailure(Call<MovieEntity> call, Throwable t) {
resultTV.setText(t.getMessage());
}
});
}
以上為沒有經過封裝的、原生態的Retrofit寫網路請求的程式碼。 我們可以封裝建立Retrofit和service部分的程式碼,然後Activity用建立一個Callback作為引數給Call,這樣Activity中只關注請求的結果,而且Call有cancel方法可以取消一個請求,好像沒Rxjava什麼事了,我覺得可以寫到這就下班了~
接下來我們要面對的問題是這樣的 如果我的Http返回資料是一個統一的格式,例如
{
"resultCode": 0,
"resultMessage": "成功",
"data": {}
}
我們如何對返回結果進行一個統一的處理呢?
另外,我的ProgressDialog的show方法應該在哪呼叫呢?看樣子只能在getMovie()這個方法裡面呼叫了,換個地方發出請求就要在對應的Listener裡面寫一遍show()的程式碼,其實挺鬧心。
而且錯誤請求我也想集中處理掉不要貼重複的程式碼。
我們先來看結合了Rxjava之後,事情有沒有變化的可能。當然即便是不用Rxjava,依舊能夠做很多的封裝,只是比較麻煩。
如需檢視專案程式碼 --> 程式碼地址:
https://github.com/tough1985/RxjavaRetrofitDemo
選擇Tag -> step1
1.3 新增Rxjava
Retrofit本身對Rxjava提供了支援。
新增Retrofit對Rxjava的支援需要在Gradle檔案中新增
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'
當然我們已經新增過了。
然後在建立Retrofit的過程中新增如下程式碼:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
這樣一來我們定義的service返回值就不在是一個Call了,而是一個Observable
重新定義MovieService
public interface MovieService {
@GET("top250")
Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}
getMovie方法改為:
//進行網路請求
private void getMovie(){
String baseUrl = "https://api.douban.com/v2/movie/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
MovieService movieService = retrofit.create(MovieService.class);
movieService.getTopMovie(0, 10)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<MovieEntity>() {
@Override
public void onCompleted() {
Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
resultTV.setText(e.getMessage());
}
@Override
public void onNext(MovieEntity movieEntity) {
resultTV.setText(movieEntity.toString());
}
});
}
這樣基本上就完成了Retrofit和Rxjava的結合,但是我知道你們當然不會滿意的。
接下來我們把建立Retrofit的過程封裝一下,然後希望Activity建立Subscriber物件傳進來。
如需檢視專案程式碼 --> 程式碼地址:
選擇Tag -> step2
1.4 將請求過程進行封裝
建立一個物件HttpMethods
public class HttpMethods {
public static final String BASE_URL = "https://api.douban.com/v2/movie/";
private static final int DEFAULT_TIMEOUT = 5;
private Retrofit retrofit;
private MovieService movieService;
//構造方法私有
private HttpMethods() {
//手動建立一個OkHttpClient並設定超時時間
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
retrofit = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
movieService = retrofit.create(MovieService.class);
}
//在訪問HttpMethods時建立單例
private static class SingletonHolder{
private static final HttpMethods INSTANCE = new HttpMethods();
}
//獲取單例
public static HttpMethods getInstance(){
return SingletonHolder.INSTANCE;
}
/**
* 用於獲取豆瓣電影Top250的資料
* @param subscriber 由呼叫者傳過來的觀察者物件
* @param start 起始位置
* @param count 獲取長度
*/
public void getTopMovie(Subscriber<MovieEntity> subscriber, int start, int count){
movieService.getTopMovie(start, count)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
用一個單例來封裝該物件,在構造方法中建立Retrofit和對應的Service。 如果需要訪問不同的基地址,那麼你可能需要建立多個Retrofit物件,或者乾脆根據不同的基地址封裝不同的HttpMethod類。
我們回頭再來看MainActivity中的getMovie方法:
private void getMovie(){
subscriber = new Subscriber<MovieEntity>() {
@Override
public void onCompleted() {
Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
resultTV.setText(e.getMessage());
}
@Override
public void onNext(MovieEntity movieEntity) {
resultTV.setText(movieEntity.toString());
}
};
HttpMethods.getInstance().getTopMovie(subscriber, 0, 10);
}
其中subscriber是MainActivity的成員變數。
如需檢視專案程式碼 --> 程式碼地址:
選擇Tag -> step3
2.相同格式的Http請求資料該如何封裝
第二部分和第三部分我參考了知乎上的一個問答: RxJava+Retrofit,在聯網返回後如何先進行統一的判斷? 不過沒有完整的示例,所以在這寫一個完整的示例出來。
這個段落我們來聊一下有些Http服務返回一個固定格式的資料的問題。 例如:
{
"resultCode": 0,
"resultMessage": "成功",
"data": {}
}
大部分的Http服務可能都是這樣設定,resultCode和resultMessage的內容相對比較穩定,而data的內容變化多端,72變都不一定夠變的,有可能是個User物件,也有可能是個訂單物件,還有可能是個訂單列表。 按照我們之前的用法,使用Gson轉型需要我們在建立subscriber物件是指定返回值型別,如果我們對不同的返回值進行封裝的話,那可能就要有上百個Entity了,看著明明是很清晰的結構,卻因為data的不確定性無奈了起來。
少年,不必煩惱,來來來~ 老衲賜你寶典葵花,老衲就是練了這個才出家。。。
我們可以建立一個HttpResult類
public class HttpResult<T> {
private int resultCode;
private String resultMessage;
private T data;
}
如果data是一個User物件的話。那麼在定義Service方法的返回值就可以寫為
Observable<HttpResult<User>>
這樣一來HttpResult就相當於一個包裝類,將結果包裝了起來,但是在使用的時候要給出一個明確的型別。
在上面的示例中,我也建立了一個HttpResult類,用來模仿這個形式,將其中的Subject單獨封裝了起來。
public class HttpResult<T> {
//用來模仿resultCode和resultMessage
private int count;
private int start;
private int total;
private String title;
//用來模仿Data
private T subjects;
}
這樣泛型的時候就要寫為:
Observable<HttpResult<List<Subject>>>
如需檢視專案程式碼 --> 程式碼地址:
選擇Tag -> step4
3.相同格式的Http請求資料統一進行預處理
既然我們有了相同的返回格式,那麼我們可能就需要在獲得資料之後進行一個統一的預處理。
當接收到了一個Http請求結果之後,由於返回的結構統一為
{
"resultCode": 0,
"resultMessage": "成功",
"data": {}
}
我們想要對resultCode和resultMessage先做一個判斷,因為如果resultCode
== 0
代表success,那麼resultCode
!= 0
時data一般都是null。
Activity或Fragment對resultCode和resultMessage基本沒有興趣,他們只對請求狀態和data資料感興趣。
基於這種考慮,我們在resultCode != 0
的時候,丟擲個自定義的ApiException。這樣就會進入到subscriber的onError中,我們可以在onError中處理錯誤資訊。
另外,請求成功時,需要將data資料轉換為目標資料型別傳遞給subscriber,因為,Activity和Fragment只想拿到和他們真正相關的資料。
使用Observable的map方法可以完成這一功能。
在HttpMethods中建立一個內部類HttpResultFunc,程式碼如下:
/**
* 用來統一處理Http的resultCode,並將HttpResult的Data部分剝離出來返回給subscriber
*
* @param <T> Subscriber真正需要的資料型別,也就是Data部分的資料型別
*/
private class HttpResultFunc<T> implements Func1<HttpResult<T>, T>{
@Override
public T call(HttpResult<T> httpResult) {
if (httpResult.getResultCode() != 0) {
throw new ApiException(httpResult.getResultCode());
}
return httpResult.getData();
}
}
然後我們的getTopMovie方法改為:
public void getTopMovie(Subscriber<List<Subject>> subscriber, int start, int count){
movieService.getTopMovie(start, count)
.map(new HttpResultFunc<List<Subject>>())
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
由於HttpResult中的泛型T就是我們希望傳遞給subscriber的資料型別,而資料可以通過httpResult的getData方法獲得,這樣我們就處理了泛型問題,錯誤處理問題,還有將請求資料部分剝離出來給subscriber
這樣我們只需要關注Data資料的型別,而不必在關心整個過程了。
需要注意一點,就是在定義Service的時候,泛型是
HttpResult<User>
//or
HttpResult<List<Subject>>
而在定義Subscriber的時候泛型是 java User //or List<Subject>
不然你會得到一個轉型錯誤。
如需檢視專案程式碼 --> 程式碼地址:
https://github.com/tough1985/RxjavaRetrofitDemo
選擇Tag -> step5
程式碼中我是用豆瓣資料模擬了HttpResult中的resultCode和resultMessage,與文件中的程式碼略有出入。
4.如何取消一個Http請求 -- 觀察者之間的對決,Observer VS Subscriber
4.1 取消一個Http請求
這一部分我們來聊一下關於取消Http請求的事情,已經Oberver和Subscriber這兩個體位我們哪個更容易給我們G點。
如果沒有使用Rxjava,那麼Service返回的是一個Call,而這個Call物件有一個cancel方法可以用來取消Http請求。那麼用了Rxjava之後,如何來取消一個請求呢?因為返回值是一個Observable。我們能做的似乎只有解除對Observable物件的訂閱,其他的什麼也做不了。
好在Retrofit已經幫我們考慮到了這一點。 答案在RxJavaCallAdapterFactory這個類的原始碼中可以找到
static final class CallOnSubscribe<T> implements Observable.OnSubscribe<Response<T>> {
private final Call<T> originalCall;
CallOnSubscribe(Call<T> originalCall) {
this.originalCall = originalCall;
}
@Override public void call(final Subscriber<? super Response<T>> subscriber) {
// Since Call is a one-shot type, clone it for each new subscriber.
final Call<T> call = originalCall.clone();
// Attempt to cancel the call if it is still in-flight on unsubscription.
subscriber.add(Subscriptions.create(new Action0() {
@Override public void call() {
call.cancel();
}
}));
try {
Response<T> response = call.execute();
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(response);
}
} catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (!subscriber.isUnsubscribed()) {
subscriber.onError(t);
}
return;
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
}
我們看到call方法中,給subscriber新增了一個Subscription物件,Subscription物件很簡單,主要就是取消訂閱用的,如果你檢視Subscriptions.create的原始碼,發現是這樣的
public static Subscription create(final Action0 unsubscribe) {
return BooleanSubscription.create(unsubscribe);
}
利用了一個BooleanSubscription類來建立一個Subscription,如果你點進去看BooleanSubscription.create方法一切就清晰了,當接觸繫結的時候,subscriber會呼叫Subscription的unsubscribe方法,然後觸發建立Subscription時候的傳遞進來的Action0的call方法。RxJavaCallAdapterFactory幫我們給subscriber新增的是call.cancel(),
總結起來就是說,我們在Activity或者Fragment中建立subscriber物件,想要取消請求的時候呼叫subscriber的unsubscribe方法就可以了。
對不起這一節有太多的Subscriber和Subscription以及Observer和Observable,老衲當時看的時候也是不知道吐了多少次了,習慣了就好了。
4.2 為什麼會提到Oberver
提到Observer的過程是這樣的。由於Subscriber一旦呼叫了unsubscribe方法之後,就沒有用了。且當事件傳遞到onError或者onCompleted之後,也會自動的解綁。這樣出現的一個問題就是每次傳送請求都要建立新的Subscriber物件。
這樣我們就把注意力放到了Observer,Observer本身是一個介面,他的特性是不管你怎麼用,都不會解綁,為什麼呢?因為他沒有解綁的方法。所以就達到了複用的效果,一開始我一直美滋滋的用Observer。事實上,如果你用的是Observer,在呼叫Observable物件的subscribe方法的時候,會自動的將Observer物件轉換成Subscriber物件。
下面是原始碼:
public final Subscription subscribe(final Observer<? super T> observer) {
if (observer instanceof Subscriber) {
return subscribe((Subscriber<? super T>)observer);
}
return subscribe(new Subscriber<T>() {
@Override
public void onCompleted() {
observer.onCompleted();
}
@Override
public void onError(Throwable e) {
observer.onError(e);
}
@Override
public void onNext(T t) {
observer.onNext(t);
}
});
}
後來發現了問題,
問題1 無法取消,因為Observer沒有unsubscribe方法 問題2 沒有onStart方法 這個一會聊
這兩個問題是很痛苦的。所以,為了後面更好的高潮,我們還是選擇用Subscriber。
5.一個需要ProgressDialog的Subscriber該有的樣子
我們希望有一個Subscriber在我們每次傳送請求的時候能夠彈出一個ProgressDialog,然後在請求接受的時候讓這個ProgressDialog消失,同時在我們取消這個ProgressDialog的同時能夠取消當前的請求,而我們只需要處理裡面的資料就可以了。
我們先來建立一個類,就叫ProgressSubscriber,讓他繼承Subscriber。
Subscriber給我們提供了onStart、onNext、onError、onCompleted四個方法。
其中只有onNext方法返回了資料,那我們自然希望能夠在onNext裡面處理資料相關的邏輯。
onStart方法我們用來啟動一個ProgressDialog。 onError方法我們集中處理錯誤,同時也停止ProgressDialog onComplated方法裡面停止ProgressDialog
其中我們需要解決兩個問題
問題1 onNext的處理 問題2 cancel掉一個ProgressDialog的時候取消請求
我們先來解決問題1
5.1處理onNext
我們希望這裡能夠讓Activity或者Fragment自己處理onNext之後的邏輯,很自然的我們想到了用介面。問題還是泛型的問題,這裡面我們必須指定明確的型別。所以介面還是需要泛型。
我們先來定義一個介面,命名SubscriberOnNextListener
public interface SubscriberOnNextListener<T> {
void onNext(T t);
}
程式碼很簡單。再來看一下ProgressSubscriber現在的程式碼
public class ProgressSubscriber<T> extends Subscriber<T> {
private SubscriberOnNextListener mSubscriberOnNextListener;
private Context context;
public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
this.mSubscriberOnNextListener = mSubscriberOnNextListener;
this.context = context;
}
@Override
public void onStart() {
}
@Override
public void onCompleted() {
Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(T t) {
mSubscriberOnNextListener.onNext(t);
}
}
我知道傳Context不好,不過為了演示而已,大家可以自己封裝一下Toast。
MainActivity使用是這樣的:
先來定義一個SubscriberOnNextListener物件,可以在onCreate裡面建立這個物件
private SubscriberOnNextListener getTopMovieOnNext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
getTopMovieOnNext = new SubscriberOnNextListener<List<Subject>>() {
@Override
public void onNext(List<Subject> subjects) {
resultTV.setText(subjects.toString());
}
};
}
getMovie方法這麼寫:
private void getMovie(){
HttpMethods.getInstance().getTopMovie(
new ProgressSubscriber(getTopMovieOnNext, MainActivity.this),
0, 10);
}
這樣Activity或Fragment就只需要關注拿到結果之後的邏輯了,其他的完全不用操心。
如需檢視專案程式碼 --> 程式碼地址:
選擇Tag -> step6
5.2處理ProgressDialog
我們希望當cancel掉ProgressDialog的時候,能夠取消訂閱,也就取消了當前的Http請求。 所以我們先來建立個介面來處理這件事情。
public interface ProgressCancelListener {
void onCancelProgress();
}
然後我們用ProgressSubscriber來實現這個介面,這樣ProgressSubscriber就有了一個onCancelProgress方法,在這裡面取消訂閱。
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
然後我用了一個Handler來封裝了ProgressDialog。
public class ProgressDialogHandler extends Handler {
public static final int SHOW_PROGRESS_DIALOG = 1;
public static final int DISMISS_PROGRESS_DIALOG = 2;
private ProgressDialog pd;
private Context context;
private boolean cancelable;
private ProgressCancelListener mProgressCancelListener;
public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
boolean cancelable) {
super();
this.context = context;
this.mProgressCancelListener = mProgressCancelListener;
this.cancelable = cancelable;
}
private void initProgressDialog(){
if (pd == null) {
pd = new ProgressDialog(context);
pd.setCancelable(cancelable);
if (cancelable) {
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
mProgressCancelListener.onCancelProgress();
}
});
}
if (!pd.isShowing()) {
pd.show();
}
}
}
private void dismissProgressDialog(){
if (pd != null) {
pd.dismiss();
pd = null;
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS_DIALOG:
initProgressDialog();
break;
case DISMISS_PROGRESS_DIALOG:
dismissProgressDialog();
break;
}
}
}
Handler接收兩個訊息來控制顯示Dialog還是關閉Dialog。 建立Handler的時候我們需要傳入ProgressCancelListener的物件例項。
最後貼出ProgressSubscriber的完整程式碼:
public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{
private SubscriberOnNextListener mSubscriberOnNextListener;
private ProgressDialogHandler mProgressDialogHandler;
private Context context;
public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
this.mSubscriberOnNextListener = mSubscriberOnNextListener;
this.context = context;
mProgressDialogHandler = new ProgressDialogHandler(context, this, true);
}
private void showProgressDialog(){
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
private void dismissProgressDialog(){
if (mProgressDialogHandler != null) {
mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
mProgressDialogHandler = null;
}
}
@Override
public void onStart() {
showProgressDialog();
}
@Override
public void onCompleted() {
dismissProgressDialog();
Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(T t) {
mSubscriberOnNextListener.onNext(t);
}
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
}
目前為止,就封裝完畢了。以上是我在用Rxjava和Retrofit過程中踩過的一些坑,最後整合出來的,由於沒有在實際的專案中跑過,有問題的話希望能夠提出來大家討論一下,拍磚也歡迎。
現在我們再寫一個新的網路請求,步驟是這樣的: 1. 在Service中定義一個新的方法。 2. 在HttpMethods封裝對應的請求(程式碼基本可以copy) 3. 建立一個SubscriberOnNextListener處理請求資料並重新整理UI。
最後
如果你覺得寫更改執行緒的程式碼覺得也很煩的話,可以把訂閱這部分也封裝起來:
public void getTopMovie(Subscriber<List<Subject>> subscriber, int start, int count){
//原來的樣子
// movieService.getTopMovie(start, count)
// .map(new HttpResultFunc<List<Subject>>())
// .subscribeOn(Schedulers.io())
// .unsubscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(subscriber);
//修改之後的樣子
Observable observable = movieService.getTopMovie(start, count)
.map(new HttpResultFunc<List<Subject>>());
toSubscribe(observable, subscriber);
}
//新增執行緒管理並訂閱
private void toSubscribe(Observable o, Subscriber s){
o.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s);
}
讓你每次寫一個請求的時候,寫的程式碼儘量少,更多的精力放在業務邏輯本身。
最後的最後
如果你的httpResult格式本身沒有問題,但是data中的內容是這樣的:
{
"resultCode": 0,
"resultMessage": "成功",
"data": {"user": {}, "orderArray": []}
}
這樣的情況還能不能繼續使用這樣的框架呢? 我的解決方法是封裝一個類,把user和orderArray作為類的屬性。 但是如果你的伺服器一會data本身是一個完整的user資料,一會又是這樣: "data":
{"user": {}, "orderArray": []}
那我覺得你有必要跟你的服務端好好聊聊了,請他吃頓飯和頓酒,大不了獻出菊花就是了。
但是如果服務已經上線了!!!
對不起,騷年......
老衲會在你墳前念300遍Thinking in java替你超度的~
希望你用Retrofit和Rxjava的新體位能夠享受到新的高潮。
相關文章
- 初探RxJava以及結合Retrofit的使用RxJava
- Android RxJava系列三: 與Retrofit2結合使用和封AndroidRxJava
- [譯] Retrofit官方文件最佳實踐
- Retrofit與LiveData結合LiveData
- RxJava2 實戰知識梳理(4) 結合 Retrofit 請求新聞資訊RxJava
- IBM,將技術與最佳實踐方法結合(轉)IBM
- Retrofit + RxJavaRxJava
- Android基於Retrofit2.0+RxJava 封裝的超好用的RetrofitClient工具類(完美結合RxJava)(六)...AndroidRxJava封裝client
- Rxjava2與Retrofit2的使用RxJava
- OkHttp、rxJava、Retrofit聯合網路請求(一)HTTPRxJava
- OkHttp、rxJava、Retrofit聯合網路請求(二)HTTPRxJava
- Kotlin中Retrofit與RxJava的簡單封裝KotlinRxJava封裝
- RxJava + Retrofit原始碼解析RxJava原始碼
- 使用Retrofit+RxJava實現網路請求RxJava
- RxJava+Retrofit+Gson實現網路請求RxJava
- RxJava如何結合觀察者與鏈式處理RxJava
- Retrofit2<三> rxJava 分析RxJava
- Retrofit+Rxjava的資料請求RxJava
- 機器學習實踐:如何將Spark與Python結合?機器學習SparkPython
- Typescript結合React實踐TypeScriptReact
- 使用Retrofit+RxJava實現帶進度下載RxJava
- Android實現Rxjava2+Retrofit完美封裝AndroidRxJava封裝
- MVP+Retrofit+Rxjava在專案中實戰解析MVPRxJava
- 【譯】jQuery事件繫結的最佳實踐jQuery事件
- Java最佳實踐小結 - jonathangilesJava
- Gulp 結構化最佳實踐
- RxJava2 實戰知識梳理(15) 實現一個簡單的 MVP + RxJava + Retrofit 應用RxJavaMVP
- Retrofit2+RxJava 簡單使用RxJava
- RxJava + Retrofit完成網路請求RxJava
- PHP 與 UTF-8 的最佳實踐PHP
- PHP 與 UTF-8的最佳實踐PHP
- WebSocket簡介與最佳實踐Web
- MobX流程分析與最佳實踐
- 分分鐘使用Retrofit+Rxjava實現網路請求RxJava
- [譯] 搜尋結果頁的最佳實踐
- Oracle高可用最佳實踐總結Oracle
- 資料要素流通與隱私增強計算的結合實踐
- 選擇結構還是類?C#中的最佳實踐與效能最佳化指南C#