初識Retrofit2.0

1.Retrofit簡介
Retrofit無疑是當下最流行的Android網絡請求框架了,是Square提供的開源產品。官方網站是這樣介紹Retrofit的—-A type-safe HTTP client for Android and Java,爲Android平臺的應用提供一個類型安全的HTTP客戶端。Retrofit 是一套註解形式的網絡請求封裝庫,它的強大在於讓代碼結構更加清晰,它可以直接解析JSON數據變成JAVA對象,支持回調操作,處理不同的結果。

2.準備工作

添加依賴:
在AndroidStudio的項目中,在build.gradle文件中添加以下引用:

 compile 'com.squareup.retrofit2:retrofit:2.1.0'

數據準備:
使用okhttp請求網絡數據的時候,我們需要把服務器返回的JSON數據手動轉換成我們的Java對象。而在上文我們提到,Retrofit可以直接解析JSON數據變成JAVA對象,這也是Retrofit靈活與強大的體現。看看怎麼實現的

 compile 'com.squareup.retrofit2:converter-gson:2.1.0'

首先添加以上引用,這裏除了gson以外,還有其他的選擇。Retrofit自動轉化的核心就是根據服務器返回的json數據定製一個javabean,舉個例子:

這裏寫圖片描述

服務器返回的很常見的一種數據類型,jsonobject對象裏面包括一個jsonarray數組,數組裏麪包括很多jsonobject對象。我們需要拿到的就是這些jsonobject裏的id與name的。看看定製的javabean該怎麼寫:

public class CityManager {

    private List<CityBean> cities;

    public List<CityBean> getCities() {
        return cities;
    }

    public void setCities(List<CityBean> cities) {
        this.cities = cities;
    }

    public class CityBean {
        private String id;
        private String name;

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

    }
}

這裏有一點需要特別注意的是:保證我們定製的javabean對象的字段要和服務器返回的數據字段一一對應,不然解析會出錯

3.基本使用

1.get請求
請求城市數據的url爲:
http://111.111.1.11/Base/getCities?clientVersion=205002&version=1622
請求方式爲get,請求參數爲clientVersion與version,請求數據爲城市的id與name,那麼使用Retrofit完成數據請求的流程如下:

public class ApiService {

    public static final String RES_GET_CITIES_LIST = "Base/getCities";

    public interface CityService {
        @GET(RES_GET_CITIES_LIST)
        Call<CityManager> getCity
        (@QueryMap Map<String, String> queryMap);
    }

 }

retrofit在使用過程中,需要定義一個接口對象,@GET標識爲get請求,@GET中所填寫的value值和baseUrl組成完整的路徑,baseUrl在構造retrofit對象時給出。@QueryMap 標識爲接口查詢的關鍵字,這裏需要的參數有兩個,所以我使用了@QueryMap,與下面這種寫法是等價的:

Call<CityManager> getCity
(@Query("clientVersion") String clientVersion, @Query("version") String version);

接口中的方法必須要有返回值,這裏將我們定製的javabean對象傳進去即可。

    public static final String BASE_URL = "http://111.111.1.11/";
    Map<String, String> queryMap = new HashMap<>();
    queryMap.put("clientVersion", "205002");
    queryMap.put("version", "1622");
    Retrofit  retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    ApiService.CityService cityService = retrofit.create(ApiService.CityService.class);
    Call<CityManager> call = cityService.getCity(queryMap);
        call.enqueue(new Callback<CityManager>()
        {
            @Override
            public void onResponse(Call<CityManager> call, Response<CityManager> response)
            {
               ArrayList<String> cityNames = new ArrayList<>();
               ArrayList<String> cityIds = new ArrayList<>();
               for (CityManager.CityBean city : response.body().getCities()) {
                    cityNames.add(city.getName());
                    cityIds.add((city.getId()));
               }

            }

            @Override
            public void onFailure(Call<CityManager> call, Throwable t)
            {
              //進行異常情況處理
            }
        });

Retrofit的構建使用的是構造者模式,指定一個baseUrl,添加一個對象轉換器,用於將服務器返回的數據轉換成對應實體類對象。構造完成以後,調用create方法就可以拿到我們的接口實例。然後再調用我們之前定義好的獲取城市的方法,得到一個call對象,通過call.enqueue即可完成異步的網絡請求。最後在數據請求成功的時候,通過response.body()即可拿到我們定義在Call< T >中需要返回的對象,數據請求失敗的時候,進行異常的處理。

2.post請求
同樣是上面的url,如果改爲post請求,要求提交的參數有兩個,userId:1001,userName:kaikai,那我們應該怎樣實現呢:

public class ApiService {

    public static final String RES_GET_CITIES_LIST = "Base/getCities";
    public interface CityService {
        @POST(RES_GET_CITIES_LIST)
        @FormUrlEncoded
        Call<CityManager> getCity
        (@QueryMap Map<String, String> queryMap,@FieldMap Map<String, String> queryBody);
    }

 }

@POST標識爲post請求,@FormUrlEncoded 與 @FieldMap註解結合表示以表單的方式傳遞鍵值對,與下面這兩種寫法是等價的:


Call<CityManager> getCity
(@QueryMap Map<String, String> queryMap, @Field("userId") String userId, @Field("userName") String userName);

這種寫法很好理解,將FieldMap拆分成了兩個Field

 Call<CityManager> getCity(@QueryMap Map<String, String> queryMap, @Body User user);

@Body註解標識的是我們的post參數對象,在使用的時候是:

cityService.getCity(queryMap,new User("1001","kaikai"));

與之對應的User實體類爲:

public class User {

    private String userId;
    private String userName;

    public User(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

}

感覺這種請求方式靈活性不是很高,需要通過傳參來構造一個參數對象。沒有@FormUrlEncoded 與 @FieldMap這種方式靈活。

    Map<String, String> queryBody = new HashMap<>();
    queryMap.put("userId", "1001");
    queryMap.put("userName", "kaikai");
    Call<CityManager> call = cityService.getCity(queryMapqueryBody);

請求網絡數據的時候,以上是需要變化的地方,其他的地方保持不變。

3.動態請求url
請求城市的url:
http://111.111.1.11/Base/getCities/id/?clientVersion=205002&version=1622
假如其中的userId是動態變化的,請求方式爲post,請求參數不變。那麼我們的url也要進行變化:

public static final String RES_GET_CITIES_LIST = "Base/getCities/{id}/";

其中{id}可以理解爲佔位符,實際使用中會通過@Path註解對所標註的參數進行替換:

public interface CityService {
        @POST(RES_GET_CITIES_LIST)
        @FormUrlEncoded
        Call<CityManager> getCity
        (@Path("id") String id,@QueryMap Map<String, String> queryMap,@FieldMap Map<String, String> queryBody);
    }

其他地方保持不變即可

4.常用配置

1.設置打印攔截器

 compile 'com.squareup.okhttp3:logging-interceptor:3.4.0-RC1'

首先添加依賴,進行log的打印

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

HttpLoggingInterceptor 是一個攔截器,用於輸出網絡請求和結果的 Log,可以配置 level 爲 BASIC / HEADERS / BODY,查看源碼它們級別依次是:

Logs request and response lines
Logs request and response lines and their respective headers
Logs request and response lines and their respective headers and bodies (if present)

這裏我們選擇BODY即可,會打印出網絡請求的url,頭部信息headers,返回數據bodies所有信息

2.統一設置headers

Retrofit 2.0支持在每個請求方法的上面添加註解進行設置header:

@Headers("Content-Type: application/json")

但是這樣的麻煩之處就是每次請求都得加上,所以就有了以下的方式:

        Interceptor interceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request()
                        .newBuilder()
                        .addHeader("Content-Type", "charset=UTF-8")
                        .addHeader("Connection", "keep-alive")
                        .build();
                return chain.proceed(request);
            }
        };

這裏具體參數以服務器要求而定

3.設置連接與讀取超時

       OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(logging)
                .addInterceptor(interceptor)
                .build();

設置連接超時以及讀取超時,然後將我們設置的攔截器添加進來。最後記得調用Retrofit.Builder()的.client(okHttpClient)方法,將我們已經設置好的okHttpClient關聯好。

5.簡單封裝

/**
 * Created by tangyangkai on 16/6/29.
 */
public class ApiWrapper {

    public static final String BASE_URL = "http://111.111.1.11/";
    private Retrofit retrofit;
    private static ApiWrapper instance;
    private String token;


    private ApiWrapper() {


        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);


Interceptor interceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request()
                        .newBuilder()
                        .addHeader("Content-Type", "charset=UTF-8")
                        .addHeader("Connection", "keep-alive")
                        .build();
                return chain.proceed(request);
            }
        };


        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(logging)
                .addInterceptor(interceptor)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();
    }

    public static ApiWrapper getInstance() {
        if (instance == null) {
            synchronized (ApiWrapper.class) {
            if (instance == null) {
                instance = new ApiWrapper();
                }
            }
        }
        return instance;
    }

    public <T> T create(Class<T> services) {
        return retrofit.create(services);
    }

}

單例模式構建的一個ApiWrapper,這樣就不用每次請求網絡數據的時候去構建一個retrofit,使用的時候也很方便:

ApiService.CityService cityService = ApiWrapper.getInstance().create(ApiService.CityService.class);
Call<CityManager> call = cityService.getCity(queryMap);

其他的代碼與前面的保持一致即可。

Retrofit的靈活與強大遠不止這些,比如設置網絡緩存,管理cookie,自定義轉換器等,以後項目中有用到的地方再更新博客。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章