Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Retrofit2.0+RxJava+RxAndroid——網絡請求框架

Retrofit2.0+RxJava+RxAndroid——網絡請求框架

編輯:關於Android編程

最近看了很多關於Retrofit和Rxjava的文檔介紹。終於在弄清Rxjava後順利的弄懂了Retrofit。

網上有很多人都介紹了它們的聯合使用,但是我看過之後理解不是太好。可能我太笨。
不過,今天寫這篇博客的目的就是想來說說它們之間如何使用以及使用的時候遇到的坑。

這兩者的關系並不大,但是聯合在一起使用是非常簡便的。Rxjava的響應式編程加上Retrofit的注解式請求用起來是非常爽的。
並且Retrofit內置的是Okhttp,所以這更加的讓Retrofit變得強大。

如果在看這篇博客的時候你對java注解、Rxjava還有Okhttp還不夠了解,建議先去了解這兩個東西。

那麼接下來切入正題。

先來加入依賴庫:

    compile 'io.reactivex:rxjava:1.0.14'
    compile 'io.reactivex:rxandroid:1.0.1'
    compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
    compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
    compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'

沒錯,你需要的庫就是這麼多。若要聯合它們三者使用,就必須這麼多。
我來按順序介紹。
1,Rxjava庫
2,RxAndroid庫
3,Retrofit適配Rxjava的庫
4,Retrofit庫
5,Retrofit適配Gson的庫(添加了這個庫後不用再添加Gson庫,因為已經內置)

另外還要有Okhttp依賴庫。在android sdk中已經內置。似乎Retrofit中也內置了Okhttp,所以我項目中沒有加入okhttp依賴庫,但是okhttp依舊可以使用。

這裡需要說明一下,第五個依賴庫根據你的項目需求來添加。
一般的項目來說都是使用json數據的。若你的項目是使用xml或者其他的數據格式,那麼對應的添加。
(以下版本號需要與retrofit版本號保持一致,並且以retrofit官網給出的版本號為准。)

1)Gson: compile 'com.squareup.retrofit2:converter-gson:2.0.1'

2)Jackson: compile 'com.squareup.retrofit2:converter-jackson:2.0.1'

3)Moshi: compile 'com.squareup.retrofit2:converter-moshi:2.0.1'

4)Protobuf: compile 'com.squareup.retrofit2:converter-protobuf:2.0.1'

5)Wire: compile 'com.squareup.retrofit2:converter-wire:2.0.1'

6)Simple XML: compile 'com.squareup.retrofit2:converter-simplexml:2.0.1'

7)Scalars (primitives, boxed, and String): compile 'com.squareup.retrofit2:converter-scalars:2.0.1'

好了。依賴庫加完以後。我們就開始請求了。

我們先來個測試url

private String ip = "http://gc.ditu.aliyun.com/geocoding?a=上海市&aa=松江區&aaa=車墩鎮";

這個是阿裡雲根據地區名獲取經緯度接口。
返回json數據

{
    "lon":120.58531,
    "level":2,
    "address":"",
    "cityName":"",
    "alevel":4,
    "lat":31.29888
}

我們先來寫好實體類AliAddrsBean和IndexRequestBean

public class AliAddrsBean {
    private double lon;
    private int level;
    private String address;
    private String cityName;
    private int alevel;
    private double lat;
     //get/set方法忽略
    }
public class IndexRequestBean {
    private String a;//一級城市
    private String aa;//二級城市
    private String aaa;//三級城市
    //get/set方法忽略
}

Retrofit的請求管理類RetrofitManage(先寫成單例)

private RetrofitManage() {
    }

    public static RetrofitManage getInstance() {
        return RetrofitManager.retrofitManage;
    }

    private static class RetrofitManager {
        private static final RetrofitManage retrofitManage = new RetrofitManage();
    }

定義一個發送網絡請求的方法sendRequest()

public void sendRequest(String url) {      
        //每一個Call實例可以同步(call.excute())或者異步(call.enquene(CallBack callBack))的被執行,
        //每一個實例僅僅能夠被使用一次,但是可以通過clone()函數創建一個新的可用的實例。
        //默認情況下,Retrofit只能夠反序列化Http體為OkHttp的ResponseBody類型
        //並且只能夠接受ResponseBody類型的參數作為@body
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl(url)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 使用RxJava作為回調適配器
                .addConverterFactory(GsonConverterFactory.create()) // 使用Gson作為數據轉換器
                .build();

Retrofit 實例化 注意事項:
1,每一個Call實例的用法和okhttp的call幾乎一樣。
2,call只能被使用一次,若再次調用會拋出異常。如果需要多次使用請使用clone
3,默認反序列化OkHttp的ResponseBody類型
4,默認只能接受ResponseBody類型的參數作為@body
5,2.0以後,get請求和post的請求的區別在call裡面,注解寫@get和寫@post已經沒有區別了
sendRequest()方法中已經寫的很清楚了,不再多說。

承載一切請求的接口ApiService

這個類我要分開寫,因為內容實在太多。

規則:每一個函數都必須有提供請求方式和相對URL的Http注解

Retrofit提供了5種內置的注解:GET、POST、PUT、DELETE和HEAD

注解中指定的資源是相對的URL
注解中指定的資源是相對的URL
注解中指定的資源是相對的URL

為啥說三遍,不解釋。
這是第一個細節也是第一個門檻。
注解裡到底寫的是什麼?

我們先來看看一個簡單的、迷茫的get請求。

@GET("search/repositories")
Call queryRetrofitByGetCall(
                                      @Query("name")String name,
                                      @Query("pwd")String pwd);

url呢?
注解裡寫的啥?
query又是啥?
怎麼調用?
怎麼返回?
這是我在網上看到最多的示例。
我就搞不懂了,給一個初學者寫這麼一個東西誰看得懂?

所以,我來一步一步的解釋清楚,寫一個易懂的例子。

先來回顧一下我們的url:http://gc.ditu.aliyun.com/geocoding?a=上海市&aa=松江區&aaa=車墩鎮

參照這個url我們來寫一個get請求:

@GET("http://gc.ditu.aliyun.com/geocoding?a=上海市&aa=松江區&aaa=車墩鎮")
    Call getIndexContent();

這麼看就明白了吧,注解中就是一個url而已。
看看怎麼調用的

Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl(url)         .addCallAdapterFactory(RxJavaCallAdapterFactory.create())              .addConverterFactory(GsonConverterFactory.create()) 
                .build();
        ApiService service = retrofit.create(ApiService.class);

        Call requestInde = service.getIndexContent();
        requestInde.execute();//同步請求,和okhttp的使用方法一樣

這就是最簡單粗暴的調用方法。這裡需要注意的是,我們還需要傳入一個實體類作為返回數據的解析依據。

這裡我們就是傳入的AliAddrsBean。Retrofit已經幫我們把數據解析好了。

可以看出來我們在外面調用了.baseUrl(url) ,為什麼在注解中還需要寫url呢?
這就關系到很多的細節和坑了。

現在我們假定我們的url是這樣的:http://gc.ditu.aliyun.com/geocoding?

如果我們在注解裡面這麼寫:
(以下有坑)

@GET("a=上海市&aa=松江區&aaa=車墩鎮")
    Call getIndexContentOne();

看似是對的,其實在我做demo的時候用okhttp請求成功但是用retrofit請求一直失敗。
究其原因就是:注解中必須要有一部分的url地址,不能光是請求體。

所以,修改代碼:
請求url:http://gc.ditu.aliyun.com/

以下是正解:

@GET("geocoding?a=上海市&aa=松江區&aaa=車墩鎮")
    Call getIndexContentOne();

這麼一寫,果然請求正常了。

另外還有一個坑,Retrofit建議url以/結束,注解不要以/開始

我們將get請求匯入接口ApiService中:

基本get請求

    //實際上在get的開始已經有一個rul存在了
    //例如,我們的url是“http://gc.ditu.aliyun.com/”
    //那麼get注解前就已經存在了這個url,並且使用{}替換符得到的最終url是:
    // http://gc.ditu.aliyun.com/geocoding?a=蘇州市
    //參數不能為null,且不能只有url的參數,還應該包括地址的字段;正確:geocoding?a=蘇州市;錯誤:a=蘇州市
    @GET("geocoding?a=蘇州市")
    Call getIndexContentOne();

    @GET("http://gc.ditu.aliyun.com/geocoding?a=上海市&aa=松江區&aaa=車墩鎮")
    Call getIndexContent();

{ }取代塊和@Path

    //這裡需要注意的是,{}作為取代塊一定不能取代參數
    //它會報異常:URL query string "a={city}" must not have replace block. For dynamic query parameters use @Query.
    //翻譯:URL查詢字符串“= {城市}”必須沒有取代塊。動態查詢參數使用@Query。
    //所以,{}取代塊只能替換url而不能替換參數參數應該用@query
    @GET("{parameters}?a=蘇州市")
    Call getIndexContentOne(
            @Path("parameters") String parameters);

所以,取代塊只能取代url,不能取代參數,且@path的作用就是專職於取代塊
調用的時候把參數傳進來

Call requestInde = service.getIndexContentOne("geocoding");

@Query 鍵值對傳參

看到那麼多參數請求,肯定有簡單的方法,單個參數添加

//看到那麼多參數請求,肯定有簡單的方法,單個參數添加
    @POST("geocoding?")
    Call getIndexContentTow(
            @Query("a") String key1,
            @Query("aa") String key2,
            @Query("aaa") String key3
    );

調用:

Call requestInde = service.getIndexContentTow("蘇州市","蘇州市","蘇州市");

@query的作用就相當於拼接字符串:a=上海市&aa=松江區&aaa=車墩鎮

@QueryMap 參數集合

有時候我們參數很多,一個一個的用@query去傳肯定不方便。

//看到那麼多參數請求,肯定有簡單的方法,多個參數添加使用map
    @GET("geocoding?")
    Call getIndexContentThree(
            @QueryMap Map options
    );

調用:

Map map = new HashMap();
map.put("a", "上海市");
map.put("aa", "黃浦區");
Call requestInde = service.getIndexContentThree(map);

@Body 請求體

我們可以把參數封裝成一個實體類,然後傳過去。這麼做比map傳參更加方便。

    //可以通過@Body注解指定一個對象作為Http請求的請求體
    //類似於一二三級城市的參數都放在的請求體indexRequestBean裡面
    @POST("geocoding?")
    Call getIndexContentFour(
            @Body IndexRequestBean indexRequestBean);

調用

IndexRequestBean indexRequestBean = new IndexRequestBean();
indexRequestBean.setA("上海市");
indexRequestBean.setAa("松江區");
indexRequestBean.setAaa("車墩鎮");
Call requestInde = service.getIndexContentFour(indexRequestBean);

這種做法得到的結果一樣,但是方便了許多。

@FormUrlEncoded 和@Field 發送表單數據

@FormUrlEncoded注解的時候,將會發送form-encoded數據

//函數也可以聲明為發送form-encoded(表單形式)和multipart(多部分)數據。
    //當函數有@FormUrlEncoded注解的時候,將會發送form-encoded數據,
    //每個鍵-值對都要被含有名字的@Field注解和提供值的對象所標注(這他媽是繞口令嗎?)
    //每個鍵值對的寫法都是用注解@field標識的,表單形式的數據
    @FormUrlEncoded
    @POST("geocoding?")
    Call getIndexContentFive(
            @Field("a") String city,
            @Field("aa") String citys,
            @Field("aaa") String cityss
    );

調用:

Call requestInde = service.getIndexContentFive("蘇州市","蘇州市","蘇州市");

@Multipart和@Part 發送字節流數據

我們有時候要在發送請求前規定數據的編碼格式,那麼我們就可以用這個注解來解決。

    //當函數有@Multipart注解的時候,將會發送multipart數據,
    // Parts都使用@Part注解進行聲明
    //Multipart parts要使用Retrofit的眾多轉換器之一或者實現RequestBody來處理自己的序列化。
    //這個可以用於傳文件,可以改變傳值的編碼,默認utf_8
    @Multipart
    @POST("geocoding?")
    Call getIndexContentSix(
            @Part("a") RequestBody city,
            @Part("aa") RequestBody citya,
            @Part("aaa") RequestBody cityaa);

可能這樣看不太懂,那麼我們就看看如何調用的就懂了

RequestBody requestBody1 = RequestBody.create(MediaType.parse("UTF-8"), "蘇州市");//如果傳值為null,則默認utf——8
//        RequestBody requestBody2 = RequestBody.create(MediaType.parse("UTF-8"), "蘇州市");
//        RequestBody requestBody3 = RequestBody.create(MediaType.parse("UTF-8"), "蘇州市");
//        Call requestInde = service.getIndexContentSix(requestBody1,requestBody2,requestBody3);

當然,既然是requestbody類型,我們就可以用它來穿文件了。

RequestBody requestBody1 = RequestBody.create(MediaType.parse("UTF-8"), new File("aaa"));//如果傳值為null,則默認utf——8

@Headers給函數設置header

對於給一個請求設置header我在日志中看到打印了header的內容。
如果我們每次都要給服務器一些固定參數,,例如版本號,請求接口版本,key等。我們就可以用它來設置在http請求的頭裡。

//可以使用@Headers注解給函數設置靜態的header
    @Headers({"key:web_service_key","web_vsersion:1.01","app_version:1.02"})
    @GET("geocoding?")
    Call getIndexContentSeven(
            @Query("a") String city
    );

調用和get請求沒有區別。是不是很方便?

@Header 單個參數的header

我感覺這個沒什麼卵用。。。
不過還是貼出來使用方法

//可以使用@Header注解動態的更新一個請求的header。必須給@Header提供相應的參數,
    //如果參數的值為空header將會被忽略,否則就調用參數值的toString()方法並使用返回結果
    @GET("geocoding?")
    Call getIndexContentNine(
            @Header("a") String city
    );

適配Rxjava

這麼多的Retrofit都是返回的call,我們應該如何適配Rxjava呢?
Retrofit默認是可以適配Rxjava的。
所以我們要做的就是:

  //我們適配Rxjava的時候,只需要將返回結果的call變成Rxjava的被訂閱者Observable即可
    @GET("geocoding?")
    Observable getIndexContentEleven(
            @Query("a") String city);

調用:

Observable requestInde = service.getIndexContentEleven("蘇州市");

我們得到了帶有網絡請求數據的Observable對象後我們就可以依照Rxjava來做一系列的響應式編程了。

這裡還有一個坑,先看我們如何處理數據
(以下有坑)

 requestInde.subscribe(new Action1() {
                    @Override
                    public void call(AliAddrsBean aliAddrsBean) {
                        //這裡我只返回了成功的結果

                    }
                });

我們直接給觀察者或者叫訂閱者發送消息“嘿!網絡請求的數據回來了!”

這樣做看似沒問題,但是在我實際運行中卻報錯了。
異常表示數據處理需要在非ui線程。

那麼就很了然了。我們得到requestInde的對象後要在非ui線程中操作。

以下代碼已經填坑

Observable requestInde = service.getIndexContentEleven("蘇州市");
        requestInde.subscribeOn(Schedulers.newThread())//這裡需要注意的是,網絡請求在非ui線程。如果返回結果是依賴於Rxjava的,則需要變換線程
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1() {
                    @Override
                    public void call(AliAddrsBean aliAddrsBean) {
                        //這裡我只返回了成功的結果

                    }
                });

我們要變換線程,處理數據的時候開一個新線程。返回結果就應該在主線程了。這裡寫android的ui線程就是用到的Rxandroid中的方法。

Observable.zip打包多個網絡請求

有時候我們需要邊加載數據邊下載某個圖片或者音樂甚至是各種文件。那麼這個時候我們就要把多個網絡請求打包起來一起發出去了。

那麼這個時候我們就要借助Observable.zip這個操作符了。

先來看如何實現的

Observable.zip(
                service.getIndexContentEleven("蘇州市"),//第一個Observable對象
                service.getIndexContentEleven("上海市"),//第二個Observable對象
                new Func2() {//function1中傳入的是《1,第一個Observable對象;2,第二個Observable對象;3,返回類型》
                    @Override
                    public String call(AliAddrsBean o, AliAddrsBean o2) {
                        return o.getLat() + ":" + o2.getLat();
                    }
                })
                .subscribeOn(Schedulers.newThread())//這裡需要注意的是,網絡請求在非ui線程。如果返回結果是依賴於Rxjava的,則需要變換線程
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("test", "retrofit error " + e.getMessage());
                    }

                    @Override
                    public void onNext(String s) {//傳入的是function1中返回的對象
                        Log.i("test", "retrofit結果1" + s);
                    }
                });

我們看zip這個操作符的源碼可以得知,它能夠封裝兩個被訂閱者(Observable)對象。
並且默認還需要實現一個得到這兩個Observable對象中的數據後的二次處理方法(Func2),把這兩個對象的結果處理成一個然後發送給觀察者(subscribe)。

一開始,並不知道哪個參數對應哪個,看了源碼後才搞清楚。方法中我在注釋中已經寫的很清楚了。

這樣我們就把兩個網路請求同時進行,然後返回的結果處理成一個拿出來。達到了打包多個網絡請求的目的。

添加okhttp委托

因為Retrofit內置okhttp,所以我們也可以為它設置一個委托okhttp對象。
具體委托方式:

Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl(url)
                .client(client)//添加一個okhttp的委托
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())  
                .addConverterFactory(GsonConverterFactory.create()) 
                .build();

那麼這個委托我們能做什麼呢?
這裡我寫了一個okhttp的攔截器

//使用OkHttp攔截器可以指定需要的header給每一個Http請求
        OkHttpClient client = new OkHttpClient();
        //網絡攔截器
        client.networkInterceptors().add(new Interceptor() {
            @Override
            public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {

                com.squareup.okhttp.Response response = chain.proceed(chain.request());

                return response;
            }
        });

我們可以做一個okhttp的攔截器,可以打印日志,可以clone 。反正發揮自己的想象,為正在請求的事件做一些額外的事情。

總結

寫到這裡,這個強強聯合的框架已經寫完了。
其實我們可以感覺到,這個框架主要是Retrofit2.0+RxJava+RxAndroid+OkHttp

其中RxAndroid的目的可能就是在android中使用的時候讓網絡請求回到主線程。

而Retrofit2.0+RxJava+OkHttp則充分的讓這個網絡請求框架變得如此之簡單、之響應式、之模塊化。

我們用注解去發送請求,簡單、簡潔;用Rxjava來讓代碼塊狀化,邏輯更清晰;網絡請求有okhttp作為強大的後台可以很好的完成各種請求。

其中還有很多沒有講到的,比如Rxjava的RxBus和RxBinding方面。okhttp的更多操作。

因為Retrofit和okhttp一樣,不支持下載進度的回調。所以之類附上另一位同行的博客來解決這個問題,順便自己也學習學習。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved