1. 程式人生 > >Android RxJava操作符的學習---變換操作符---網路請求巢狀回撥

Android RxJava操作符的學習---變換操作符---網路請求巢狀回撥

  • 變換操作符的主要開發需求場景 = 巢狀回撥(Callback hell
  • 下面,我將採用一個實際應用場景例項來講解巢狀回撥(Callback hell

1. 需求場景

1.1 背景

需要進行巢狀網路請求:即在第1個網路請求成功後,繼續再進行一次網路請求

如 先進行 使用者註冊 的網路請求, 待註冊成功後回再繼續傳送 使用者登入 的網路請求

1.2 衝突

巢狀實現網路請求較為複雜,即巢狀呼叫函式

下面展示的是結合 RetrofitRxJava的基本用法,即未用操作符前

// 傳送註冊網路請求的函式方法
    private void register() {
        api.register(new RegisterRequest())
                .subscribeOn(Schedulers.io())               //在IO執行緒進行網路請求
                .observeOn(AndroidSchedulers.mainThread())  //回到主執行緒去處理請求結果
                .subscribe(new Consumer<RegisterResponse>() {
                    @Override
                    public void accept(RegisterResponse registerResponse) throws Exception {
                        Toast.makeText(MainActivity.this, "註冊成功", Toast.LENGTH_SHORT).show();
                        login();   //註冊成功, 呼叫登入的方法
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Toast.makeText(MainActivity.this, "註冊失敗", Toast.LENGTH_SHORT).show();
                    }
                });
    }


// 傳送登入網路請求的函式方法
private void login() {
        api.login(new LoginRequest())
                .subscribeOn(Schedulers.io())               //在IO執行緒進行網路請求
                .observeOn(AndroidSchedulers.mainThread())  //回到主執行緒去處理請求結果
                .subscribe(new Consumer<LoginResponse>() {
                    @Override
                    public void accept(LoginResponse loginResponse) throws Exception {
                        Toast.makeText(MainActivity.this, "登入成功", Toast.LENGTH_SHORT).show();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Toast.makeText(MainActivity.this, "登入失敗", Toast.LENGTH_SHORT).show();
                    }
                });
    }

1.3 解決方案

結合 RxJava2中的變換操作符FlatMap()實現巢狀網路請求

2. 功能說明

  • 實現功能:傳送巢狀網路請求(將英文翻譯成中文,翻譯兩次)
  1. 為了讓大家都能完成Demo,所以通過 公共的金山詞霸API 來模擬 “註冊 - 登入”巢狀網路請求
  2. 即先翻譯 Register(註冊),再翻譯 Login(登入)
  • 實現方案:採用Get方法對 金山詞霸API 傳送網路請求

採用 Gson 進行資料解析

3. 具體實現

下面我將結合 RetrofitRxJava 實現網路請求巢狀

3.1 步驟說明

  1. 新增依賴
  2. 建立 接收伺服器返回資料 的類
  3. 建立 用於描述網路請求 的介面(區別於Retrofit傳統形式)
  4. 建立 Retrofit 例項
  5. 建立 網路請求介面例項 並 配置網路請求引數(區別於Retrofit傳統形式)
  6. 傳送網路請求(區別於Retrofit傳統形式)
  7. 傳送網路請求
  8. 對返回的資料進行處理

3.2 步驟實現

步驟1: 新增依賴

a. 在 Gradle加入Retrofit庫的依賴

build.gradle

dependencies {

// Android 支援 Rxjava
// 此處一定要注意使用RxJava2的版本
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

// Android 支援 Retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'

// 銜接 Retrofit & RxJava
// 此處一定要注意使用RxJava2的版本
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

// 支援Gson解析
compile 'com.squareup.retrofit2:converter-gson:2.1.0'

}

b. 新增 網路許可權
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

步驟2:建立 接收伺服器返回資料 的類

  • 金山詞霸API 的資料格式說明如下:
// URL模板
http://fy.iciba.com/ajax.php

// URL例項
http://fy.iciba.com/ajax.php?a=fy&f=auto&t=auto&w=hello%20world

// 引數說明:
// a:固定值 fy
// f:原文內容型別,日語取 ja,中文取 zh,英語取 en,韓語取 ko,德語取 de,西班牙語取 es,法語取 fr,自動則取 auto
// t:譯文內容型別,日語取 ja,中文取 zh,英語取 en,韓語取 ko,德語取 de,西班牙語取 es,法語取 fr,自動則取 auto
// w:查詢內容
  • 示例

  • 根據 金山詞霸API 的資料格式,建立 接收伺服器返回資料 的類:

為了演示是2個網路請求,所以對應設定2個接收伺服器的資料類

<-- Translation1.java -->
public class Translation1 {
    private int status;
    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定義 輸出返回資料 的方法
    public void show() {

        Log.d("RxJava", "翻譯內容 = " + content.out);

    }
}

<-- Translation2.java -->
public class Translation2 {
    private int status;
    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定義 輸出返回資料 的方法
    public void show() {

        Log.d("RxJava", "翻譯內容 = " + content.out);

    }
}

步驟3:建立 用於描述網路請求 的介面

採用 註解 + Observable<...>介面描述 網路請求引數

GetRequest_Interface.java

public interface GetRequest_Interface {

    // 網路請求1
    @GET("ajax.php?a=fy&f=auto&t=auto&w=hi%20register")
    Observable<Translation1> getCall();

    // 網路請求2
    @GET("ajax.php?a=fy&f=auto&t=auto&w=hi%20login")
    Observable<Translation2> getCall_2();

    // 註解裡傳入 網路請求 的部分URL地址
    // Retrofit把網路請求的URL分成了兩部分:一部分放在Retrofit物件裡,另一部分放在網路請求接口裡
    // 如果接口裡的url是一個完整的網址,那麼放在Retrofit物件裡的URL可以忽略
    // 採用Observable<...>介面
    // getCall()是接受網路請求資料的方法

}

接下來的步驟均在MainActivity.java內實現(請看註釋)

MainActivity.java

public class MainActivity extends AppCompatActivity {

        private static final String TAG = "Rxjava";

        // 定義Observable介面型別的網路請求物件
        Observable<Translation1> observable1;
        Observable<Translation2> observable2;

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

            // 步驟1:建立Retrofit物件
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://fy.iciba.com/") // 設定 網路請求 Url
                    .addConverterFactory(GsonConverterFactory.create()) //設定使用Gson解析(記得加入依賴)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支援RxJava
                    .build();

            // 步驟2:建立 網路請求介面 的例項
            GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

            // 步驟3:採用Observable<...>形式 對 2個網路請求 進行封裝
            observable1 = request.getCall();
            observable2 = request.getCall_2();


            observable1.subscribeOn(Schedulers.io())               // (初始被觀察者)切換到IO執行緒進行網路請求1
                       .observeOn(AndroidSchedulers.mainThread())  // (新觀察者)切換到主執行緒 處理網路請求1的結果
                       .doOnNext(new Consumer<Translation1>() {
                        @Override
                        public void accept(Translation1 result) throws Exception {
                            Log.d(TAG, "第1次網路請求成功");
                            result.show();
                            // 對第1次網路請求返回的結果進行操作 = 顯示翻譯結果
                        }
                    })

                    .observeOn(Schedulers.io())                 // (新被觀察者,同時也是新觀察者)切換到IO執行緒去發起登入請求
                                                                // 特別注意:因為flatMap是對初始被觀察者作變換,所以對於舊被觀察者,它是新觀察者,所以通過observeOn切換執行緒
                                                                // 但對於初始觀察者,它則是新的被觀察者
                    .flatMap(new Function<Translation1, ObservableSource<Translation2>>() { // 作變換,即作巢狀網路請求
                        @Override
                        public ObservableSource<Translation2> apply(Translation1 result) throws Exception {
                            // 將網路請求1轉換成網路請求2,即傳送網路請求2
                            return observable2;
                        }
                    })

                    .observeOn(AndroidSchedulers.mainThread())  // (初始觀察者)切換到主執行緒 處理網路請求2的結果
                    .subscribe(new Consumer<Translation2>() {
                        @Override
                        public void accept(Translation2 result) throws Exception {
                            Log.d(TAG, "第2次網路請求成功");
                            result.show();
                            // 對第2次網路請求返回的結果進行操作 = 顯示翻譯結果
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(Throwable throwable) throws Exception {
                            System.out.println("登入失敗");
                        }
                    });
    }
}

3.3 測試結果