1. 程式人生 > >Android 一份詳細的Retrofit2.0基本使用總結

Android 一份詳細的Retrofit2.0基本使用總結

一、前言

今天主要是對於網路請求框架Retrofit2.0做一次總結,並通過具體例項展示一下Retrofit2.0的使用方法。

二、概述

Retrofit2.0是一款基於OkHttp的非常適用於RESTful URL格式的HTTP網路請求框架。OkHttp是Square公司貢獻的一款輕量級網路請求框架,我在這裡就不做具體介紹了,後續我會對這些主流的網路框架的優缺做個總結,RESTful是一種軟體架構風格,設計風格,而不是標準,只是提供了一組設計原則和約束條件,RESTful主要用於客戶端和伺服器互動類的軟體,基於這個風格設計的軟體,可以更簡潔、更有層次、更易於實現快取等機制(摘自百科,具體的原則和條件檢視

)。

Retrofit2.0通過註解配置網路引數,可以按照我們的規則去構造實際的HTTP請求,能夠靈活設定URL、頭部、請求體、返回值等,其還支援同步、非同步請求資料,能靈活搭配converter-gson等多種解析框架和RxJava2的響應式程式設計模式,在設計上高度解耦,在使用上靈活方便,而在效能上,更因為是整合自OkHttp具有效能好,處理速度快等優點。

三、基本使用

我覺得可以把Retrofit2.0的使用分為以下幾個步驟:

  • 在gradle檔案中新增Retrofit2.0的依賴,並在清單中新增網路請求許可權
  • 將我們的HTTP API改造成java介面,如下面的IService
  • 建立返回型別Call< T >,其中具體T類用來“接收”伺服器返回的資料
  • 通過Retrofit物件獲得IService介面的實現service,然後通過service來獲取IService中的Call< T >型別物件來進行同步或非同步請求
  • 根據實際的需要,為Retrofit配置OkHttpClient和ConverterFactory

下面我們就來拿獲取天氣資訊的小例子來看看效果,天氣網址API網址,這個例子的主要作用就是完整的簡單的使用一下Retrofit2.0,讓我們的大腦對於Retrofit2.0的使用過程有一個整體的認識,而具體的註解標識引數講解會在稍後介紹。

這是我們所新增的依賴,因為retrofit底層對於網路的預設訪問也是基於okhttp的,所以我們也得新增okhttp

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.8.0'

這是我們所新增的許可權,因為網路許可權只是普通許可權,android6.0之後也不需要動態獲取,直接在清單檔案配置即可

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

這是我們所建立的IService介面,通過@GET註解標識為get請求,@GET中所填寫的”data/sk/{location}”和後面將介紹的baseUrl(http://www.weather.com.cn/)組成完整的天氣網址,其中{location}相當於佔位符,我們可以通過weatherInfo方法中的@Path進行替換,在本例中傳入的實參locationCode即“101010100.html”

public interface IService {
    //天氣網址:http://www.weather.com.cn/data/sk/101010100.html
    @GET("data/sk/{location}")
    Call<WeatherBean> weatherInfo(@Path("location") String locationCode);
}

這是我們所需要的WeatherBean類,用來接收伺服器給我們返回的資料,由於本例中伺服器返回的json資料,所以我們可以將WeatherBean直接通過AS自帶外掛GsonFormat來自動生成欄位和方法,非常方便

這裡寫圖片描述

這是具體的WeatherBean程式碼 ,請求成功後的Json資訊模板也寫在了註釋裡,我們可以通過返回的Json資訊來自動生成欄位方法。

public class WeatherBean {
    /**
     * 測試網址:http://www.weather.com.cn/data/sk/101010100.html
     * 請求返回的Json資訊如下
     * {"weatherinfo":{"city":"北京","cityid":"101010100","temp":"27.9","WD":"南風","WS":"小於3級","SD":"28%","AP":"1002hPa","njd":"暫無實況","WSE":"<3","time":"17:55","sm":"2.1","isRadar":"1","Radar":"JC_RADAR_AZ9010_JB"}}
     */

    private WeatherinfoBean weatherinfo;

    public WeatherinfoBean getWeatherinfo() {
        return weatherinfo;
    }

    public void setWeatherinfo(WeatherinfoBean weatherinfo) {
        this.weatherinfo = weatherinfo;
    }

    public static class WeatherinfoBean {
        /**
         * city : 北京
         * cityid : 101010100
         * temp : 27.9
         * WD : 南風
         * WS : 小於3級
         * SD : 28%
         * AP : 1002hPa
         * njd : 暫無實況
         * WSE : <3
         * time : 17:55
         * sm : 2.1
         * isRadar : 1
         * Radar : JC_RADAR_AZ9010_JB
         */

        private String city;
        private String cityid;
        private String temp;
        private String WD;
        private String WS;
        private String SD;
        private String AP;
        private String njd;
        private String WSE;
        private String time;
        private String sm;
        private String isRadar;
        private String Radar;

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public String getCityid() {
            return cityid;
        }

        public void setCityid(String cityid) {
            this.cityid = cityid;
        }

        public String getTemp() {
            return temp;
        }

        public void setTemp(String temp) {
            this.temp = temp;
        }

        public String getWD() {
            return WD;
        }

        public void setWD(String WD) {
            this.WD = WD;
        }

        public String getWS() {
            return WS;
        }

        public void setWS(String WS) {
            this.WS = WS;
        }

        public String getSD() {
            return SD;
        }

        public void setSD(String SD) {
            this.SD = SD;
        }

        public String getAP() {
            return AP;
        }

        public void setAP(String AP) {
            this.AP = AP;
        }

        public String getNjd() {
            return njd;
        }

        public void setNjd(String njd) {
            this.njd = njd;
        }

        public String getWSE() {
            return WSE;
        }

        public void setWSE(String WSE) {
            this.WSE = WSE;
        }

        public String getTime() {
            return time;
        }

        public void setTime(String time) {
            this.time = time;
        }

        public String getSm() {
            return sm;
        }

        public void setSm(String sm) {
            this.sm = sm;
        }

        public String getIsRadar() {
            return isRadar;
        }

        public void setIsRadar(String isRadar) {
            this.isRadar = isRadar;
        }

        public String getRadar() {
            return Radar;
        }

        public void setRadar(String Radar) {
            this.Radar = Radar;
        }
    }
}

建立我們的Retrofit例項,通過構造者模式指定了baseUrl和Converter.Factory,後者是用於資料解析的,在本例中伺服器返回的是json資料,所以這裡直接設定了GsonConverterFactory來完成物件的轉化

    /** 去請求天氣資訊*/
    private void toRequestWeatherInfo() {
        Retrofit retrofit = new Retrofit.Builder()
                //RequestHttp.WeatherBaseUrl = "http://www.weather.com.cn/" 
                .baseUrl(RequestHttp.WeatherBaseUrl)
                //記得新增相關依賴
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IService weather = retrofit.create(IService.class);
        Call<WeatherBean> weatherBeanCall = weather.weatherInfo("101010100.html");
        //這裡只是寫了非同步請求,你也可以進行同步請求
        weatherBeanCall.enqueue(new Callback<WeatherBean>() {
            @Override
            public void onResponse(Call<WeatherBean> call, Response<WeatherBean> response) {
                Log.i("Tag123",response.body().getWeatherinfo().getCity()+"-"+response.body().getWeatherinfo().getWD());
            }

            @Override
            public void onFailure(Call<WeatherBean> call, Throwable t) {
                Log.i("Tag123","onFailure");
            }
        });
    }

使用GsonConverterFactory,記得要在gradle中新增相關依賴

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

通過上面幾步,一個完整的Retrofit請求就已經出來了,最後來看下執行結果

這裡寫圖片描述

四、註解和引數介紹

通過官方文件我們可以知道,每個方法都必須有一個HTTP註解用來提供請求方法和相對URL,Retrofit2.0的註解如下。如果你對於註解還不瞭解,可以檢視Android 註解詳解

Retrofit2.0中的註解 具體名稱
請求方法的註解 @GET、@POST、@PUT、@DELATE、@HEAD、@PATCH、@OPTIONS、@HTTP
標記註解 @FormUrlEncoded、@Multipart、@Streaming
引數註解 @Path 、@Field、 @FieldMap、 @Part、 @PartMap、 @Query、 @QueryMap、 @Body、 @Url、 @Header、 @Headers

註解的個數比較多,如果都靠死記硬背肯定容易亂套,下面我們主要根據幾個具體的情景,來看看上面註解的用法,以及註解之間的配合使用。

(1)簡單get請求,在註解中指定資源的相對URL

在一些固定的網址請求中,例如剛才我們使用到的獲取天氣介面,如果我們只需要獲取北京的天氣,那麼直接可以使用@GET註解時傳入我們所需要的全部元素

public interface IService {
    //完整北京天氣網址:http://www.weather.com.cn/data/sk/101010100.html
    //baseUrl:http://www.weather.com.cn/
    //只需要獲取北京的天氣,不考慮其他地區,那麼這個網址就變成了一個固定網址
    @GET("data/sk/101010100.html")
    Call<WeatherBean> weatherBJInfo();
}

WeatherBean類沒有展示出程式碼,你可以直接將上面完整網址在瀏覽器中開啟獲得伺服器返回的Json資料,再通過Android Studio提供的GsonFormat自動生成bean欄位方法,後面的例子同樣如此處理即可,隨後Retrofit中的部分程式碼如下

IService weather = retrofit.create(IService.class);
Call<WeatherBean> weatherBeanCall = weather.weatherBJInfo();

(2)一般get請求,動態更新請求URL

我們可以通過在@GET註解裡包含替換塊{}和在方法中使用引數@path來實現URL的動態更新,上面的demo就是用的這種方式

public interface IService {
    //完整北京天氣網址:http://www.weather.com.cn/data/sk/101010100.html
    //完整上海天氣網址:http://www.weather.com.cn/data/sk/101020100.html
    //baseUrl:http://www.weather.com.cn/
    @GET("data/sk/{location}")
    Call<WeatherBean> weatherInfo(@Path("location") String locationCode);
}

在Retrofit中的部分程式碼如下

IService weather = retrofit.create(IService.class);
//Call<WeatherBean> weatherBeanCall = weather.weatherInfo("101010100.html");
Call<WeatherBean> weatherBeanCall = weather.weatherInfo("101020100.html");

我們通過在@GET中新增一個{location}替換塊,相當於佔位符作用,當呼叫weatherInfo方法傳入@path所標註的引數locationCode後,在實際執行時就會進行相應替換。

(3)一般get請求,新增單個查詢引數

我們可以通過@Query來新增查詢引數,因為沒有找到單個引數的開放api,我就直接用自己的一個專案網址來測試了,網址並不重要,因為知識點都是一樣的

public interface IService {
    //獲取圖片列表的網址:http://h5.comeup.com.cn:8081/upweb/msg?method=listImgs
    //baseUrl:http://h5.comeup.com.cn:8081/upweb/
    @GET("msg")
    Call<QueryImgBean> imgListInfo(@Query("method") String method);
}

在Retrofit中的部分程式碼

IService picList = retrofit.create(IService.class);
Call<QueryImgBean> weatherBeanCall = picList.imgListInfo("listImgs");

我們通過@Query引數來動態的指定網址中?method=listImgs部分,其實這個網址完全可以使用@GET(“msg?method=listImgs”),因為listImgs是不變的,但是這裡主要就是演示一下@Query引數的使用方法。

(4)一般get請求,新增多個查詢引數

在實際開發裡,多個引數的查詢方法是非常常見的,我們主要通過@QueryMap引數來新增查詢引數,方法跟@Query引數大致一樣,下面我們來看一個反地理編碼的網址:

public interface IService {
    //反地理編碼網址:http://api.map.baidu.com/geocoder/v2/?location=39.904239,116.510388&output=json&pois=1&ak=Oo5xCoWby8wCGC0tUvRNQ5cv6tdGU72c
    //baseUrl:http://api.map.baidu.com/
    @GET("geocoder/v2/")
    Call<MyLocationBean> location(@QueryMap Map<String, String> map);
}

這是我們的Retrofit的部分程式碼:

        //為了方便展示程式碼,直接簡單粗暴的都寫在一起了
        Map<String, String> map = new HashMap<>();
        map.put("output","json");
        map.put("pois","1");
        //百度地圖web端ak,可能後期會失效
        map.put("ak","Oo5xCoWby8wCGC0tUvRNQ5cv6tdGU72c");
        //經緯度資訊
        map.put("location","39.904239,116.510388");
        IService picList = retrofit.create(IService.class);
        Call<MyLocationBean> location = picList.location(map);

其實使用方法跟@Query一樣,這裡是通過一個map容器來將需要的每個引數欄位拼接進請求網址裡。

(5)一般get請求,使用@Url來動態提供完整網址

我們不僅可以通過@GET(“xxx”)來靜態提供完整url,我們也能直接通過@Url來動態提供:

    //只需要獲取北京的天氣,不考慮其他地區,那麼這個網址就變成了一個固定網址
    //完整北京天氣網址:http://www.weather.com.cn/data/sk/101010100.html
    //baseUrl:http://www.weather.com.cn/
    @GET
    Call<WeatherBean> weatherBJInfoByUrl(@Url String url);

這是我們Retrofit中的部分程式碼:

IService weather = retrofit.create(IService.class);
Call<WeatherBean> weatherBeanCall = weather.weatherBJInfoByUrl("data/sk/101010100.html");

(6)簡單POST的方式,使用@Body指定一個物件作為HTTP請求體

在我們實際編寫程式碼過程中,我們經常會把一個物件轉換成Json串來作為整個HTTP的請求體來傳給伺服器,這個時候就用到了@Body這個註解,@Body註解的用法也很簡單,只需要標識一下我們的引數物件即可:

    //POST @Body方式的:http://x.x.x.x:8088/patrolapp/iniInterface/taskList.do
    //請求體:{"userid":"234"}
    //baseUrl:http://x.x.x.x:8088/
    //因為是一個專案裡的網址,所以就不公開了,但是這種方式親測有效
    @POST("patrolapp/iniInterface/taskList.do")
    Call<PostBodyReponseBeanTwo> translateWordByPost(@Body PostBodyBean body);

這是我們的Body請求體bean:

public class PostBodyBean {
    /** 因為userid欄位名字和PostBodyBean變數名字一樣,所以不需要@SerializedName("userid"),註解裡的userid欄位為json串(請求體)裡面欄位名字,Retrofit會自動對映*/
    public String userid;
    public PostBodyBean(String userid) {
        this.userid = userid;
    }
}

這是我們的Retrofit中的部分程式碼:

IService service = retrofit.create(IService.class);
Call<PostBodyReponseBeanTwo> pageVo = service.translateWordByPost(new PostBodyBean("234"));

其中PostBodyReponseBeanTwo類為接收請求資料成功後返回的json資料類,在這裡就不展示了,如果感興趣的可以檢視下Demo,程式碼都很簡單。

有的時候,我們在請求資料的時候,經常需要將一個物件的Json串作為請求網址的一個引數來傳給伺服器,在這種時候,我們就不能像上面那樣處理了,例如下面這個例子:

    //查詢資訊:http://h5.comeup.com.cn:8081/upweb/message/list?source=android&token=xxx&pageVo={"pullType":"","size":10,"orderTime":""}
    //baseUrl:http://h5.comeup.com.cn:8081/upweb/
    @POST("message/list")
    Call<PostBodyReponseJsonBean> registerPostBody(@Body RequestBody body);

這是我們的Retrofit的部分程式碼:

IService service = retrofit.create(IService.class);
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), "source=android&token=xxx&pageVo={}");
Call<PostBodyReponseJsonBean> pageVo = service.registerPostBody(body);

哈雖然可以,但這種方式比較簡單粗暴,其實更好的方法可以看下文

(7)POST的方式,使用表單的格式新增鍵值對

當在方法上存在@FormUrlEncoded時,POST提交資料方式對應的Content-Type:application/x-www-form-urlencoded,我們可以通過@POST指定部分網址,然後通過一個或多個@Field拼接引數即可,當然你也可以使用@FieldMap通過map容器一次拼接多個引數,其用法與@QueryMap大體一致,在這裡就不單獨介紹了,下面我們來看一個有道詞典翻譯api的網址:

public interface IService {
    //有道詞典翻譯:http://fanyi.youdao.com/translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=&i=love
    //當前網址翻譯單詞:love
    //baseUrl:http://fanyi.youdao.com/
    @FormUrlEncoded
    @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=")
    Call<YDbean> translateWord(@Field("i") String translatedWord);
}

這是我們的Retrofit的部分程式碼:

IService service = retrofit.create(IService.class);
Call<YDbean> ydBean = service.translateWord("love");

(8)POST的方式,使用@Multipart來上傳檔案

在這裡我就直接為大家演示一個Retrofit2上傳圖片的例子,下面是我們的IService介面:

public interface IService {
    //上傳圖片:http://image.comeup.com.cn:7002/uploadweb/imgUpload/taskImg?method=publishTaskImg&token=xxx&source=android&codeImg=需要上傳的圖片file
    //baseUrl:http://image.comeup.com.cn:7002/uploadweb/
    //因為使用的是公司的伺服器,所以token值沒有給出,例子都是親測有效的,測試機華為MATE7 6.0、OPPO R7 4.4.4
    @Multipart
    @POST("imgUpload/taskImg")
    Call<PhotoBean> upLoadPhoto(@Part MultipartBody.Part codeImg, @PartMap Map<String, RequestBody> params);
}

在這裡@Multipart允許我們使用多個@Part來拼接多個引數,@Part的用法也很簡單,例如@Part(“method”) RequestBody methodValue,如果引數過多時,我們也可以使用@PartMap來直接新增一個盛放參數的Map集合,其和上文的@FieldMap用法都是共通的,其中PhotoBean類是用來接收上傳成功後伺服器返回的Json資訊的,其程式碼如下:

public class PhotoBean {
    /**
     * 上傳圖片成功後伺服器返回的json資訊
     * JSON樣式:{"data":"files/task/20180523/1527070539100.png","errorCode":"0","errorMsg":null,"encrypt":false}
     */
    private String data;
    private String errorCode;
    private Object errorMsg;
    private boolean encrypt;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public Object getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(Object errorMsg) {
        this.errorMsg = errorMsg;
    }

    public boolean isEncrypt() {
        return encrypt;
    }

    public void setEncrypt(boolean encrypt) {
        this.encrypt = encrypt;
    }
}

接下來新增我們的許可權,不僅需要在清單中新增:

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

因為在android6.0系統以上時,需要動態獲取許可權:

    //當Android6.0系統以上時,動態獲取許可權
    private static final String[] PERMISSIONS = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE};
    //許可權的標誌
    private static final  int PERMISSION_CODES = 1001;
    private boolean permissionGranted = true;

    /** 動態的進行許可權請求*/
    @TargetApi(Build.VERSION_CODES.M)
    private void requestPermission(){
        List<String> p = new ArrayList<>();
        for(String permission :PERMISSIONS){
            if(ContextCompat.checkSelfPermission(this,permission) != PackageManager.PERMISSION_GRANTED){
                p.add(permission);
            }
        }
        if(p.size() > 0){
            requestPermissions(p.toArray(new String[p.size()]),PERMISSION_CODES);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case PERMISSION_CODES:
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED){
                    permissionGranted = false;
                }else {
                    permissionGranted = true;
                }
                break;
        }
    }

這是我們的Retrofit的部分程式碼:

        IService service = retrofit.create(IService.class);
        //type型別既可以上傳圖片,也可以上傳簡單鍵值對
        MediaType type = MediaType.parse("multipart/form-data");
        //為了直觀,圖片路徑我就直接硬編碼了
        File file = new File("sdcard/hy/pic/1527133940554.jpg");
        RequestBody photoRequestBody = RequestBody.create(type, file);
        //其中codeImg為我們網址中file需要對應的引數
        MultipartBody.Part codeImg = MultipartBody.Part.createFormData("codeImg", file.getName(), photoRequestBody);
        //這是我們需要的其他引數,如果這裡的引數是固定值,我們就可以直接寫死在@POST裡面,例如method和source引數
        Map<String, RequestBody> map = new HashMap<>();
        map.put("method", RequestBody.create(type, "publishTaskImg"));
        map.put("token", RequestBody.create(type, "xxx"));
        map.put("source", RequestBody.create(type, "android"));
        Call<PhotoBean> ydBean = service.upLoadPhoto(codeImg, map);

(9)@Header和@Headers的使用

這個我就直接根據完檔寫下用法了,這是@Header的用法,能夠動態指定頭部資訊,新增HTTP請求報文頭的目的就是為了能給我們的請求提供一些附屬資訊:

@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

這是@Headers的用法,可以靜態新增請求頭部資訊,上面的”Authorization”相當於這裡的”Cache-Control”:

@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();

同時@Headers還能夠新增多個頭部資訊:

@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

相信通過上面的介紹,你對於Retrofit2.0的註解應該已經有個簡單的認識了,其他的沒有介紹到的註解,自己可以寫個例子試一試,其實原理用法都是共通的。

五、配置OkHttpClient

有的時候,我們需要發出的請求共用一個OkHttpClient就好;有的時候,我們需要檢視一下HTTP請求的日誌資訊;有的時候,我們需要為我們的請求新增一個HTTP請求報文頭,來為我們的請求提供一些附屬資訊等等

(1)Retrofit設定單一okHttpClient

        //如果想所有請求都共用一個okHttpClient,則採用單例模式就好
        OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
        Retrofit retrofit1 = new Retrofit.Builder()
                .baseUrl(RequestHttp.REGISER_BASEURL1)
                //新增OkHttpClient,原始碼可以看到client()後會直接呼叫callFactory(okhttp3.Call.Factory factory)方法
                .client(okHttpClient.build())
                .build();
        Retrofit retrofit2 = new Retrofit.Builder()
                .baseUrl(RequestHttp.REGISER_BASEURL2)
                .client(okHttpClient.build())
                .build();

(2)新增日誌攔截器列印日誌資訊

我們需要在gradle檔案中新增依賴,注意版本與okhttp3保持一致:

    //記得要與compile 'com.squareup.okhttp3:okhttp:3.8.0'版本保持一致
    //否則會報錯:java.lang.NoClassDefFoundError: okhttp3.internal.Platform
    compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'

為okHttpClient新增需要的攔截器:

        //宣告日誌類
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        //設定日誌級別,分別為NONE、BASIC、HEADERS、BODY
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        //宣告一個OkHttpClient
        OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
        //新增攔截器
        okHttpClient.addInterceptor(httpLoggingInterceptor);

(3)自定義日誌攔截器

如果logging-interceptor不能滿足你的要求,或者你想要對於請求資料做些自己的處理,那麼可以自定義一個攔截器:

//這裡使用的匿名內部類,來實現一個日誌攔截器
okHttpClient.addInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                //獲得Request
                Request request = chain.request();
                //在這裡我們可以新增統一的header資訊
                request.newBuilder().header("token","xxx");
                //列印請求資訊
                Log.i("Tag","url:" + request.url()+"\n method:" + request.method()+"\n request-body:" + request.body()+"\n header:"+request.header("token"));
                //記錄請求的開始時間
                long startNs = System.nanoTime();
                okhttp3.Response response;
                try {
                    response = chain.proceed(request);
                } catch (Exception e) {
                    throw e;
                }
                long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
                //列印請求耗時
                Log.i("Tag","請求耗時:"+tookMs+"ms");
                return response;
            }
        });

六、自定義ConverterFactory

在我們自定義ConverterFactory之前,我覺得我們有必要先看一下GsonConverterFactory的實現,不管是在思路上還是在寫法上,這都是一個非常非常值得借鑑模仿的示例。

(1)分析GsonConverterFactory

這是我們的Converter.Factory類,我們所有自定義的ConverterFactory都是繼承於它,GsonConverterFactory同樣也不例外,在這裡我們可以看到,Converter.Factory類是一個抽象類,並提供了3個公開方法

public interface Converter<F, T> {
  T convert(F value) throws IOException;

 //為了防止程式碼片段過長,我把原始碼註釋都去掉了,你自己可以在AS中檢視
  abstract class Factory {
   //將HTTP響應體ResponseBody轉換成我們需要的bean
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
        Annotation[] annotations, Retrofit retrofit) {
      return null;
    }
    //將@Body、@Part、@PartMap註解的資料轉換為HTTP請求體RequestBody
    public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}

接下來就是GsonConverterFactory類,程式碼並不多,主要是繼承了Converter.Factory類,並重寫了responseBodyConverter和requestBodyConverter方法:

public final class GsonConverterFactory extends Converter.Factory {
  public static GsonConverterFactory create() {
    return create(new Gson());
  }

  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static GsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new GsonConverterFactory(gson);
  }

  private final Gson gson;

  private GsonConverterFactory(Gson gson) {
    this.gson = gson;
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
}

這是上面responseBodyConverter方法需要的GsonResponseBodyConverter類,主要作用就是將HTTP響應體ResponseBody轉換成我們需要的型別:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
}

這是上面requestBodyConverter方法需要的GsonRequestBodyConverter類,主要作用就是將請求資料轉換成HTTP請求體RequestBody:

final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
  private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
  private static final Charset UTF_8 = Charset.forName("UTF-8");

  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public RequestBody convert(T value) throws IOException {
    Buffer buffer = new Buffer();
    Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
    JsonWriter jsonWriter = gson.newJsonWriter(writer);
    adapter.write(jsonWriter, value);
    jsonWriter.close();
    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
  }
}

從上面兩個類中我們可以看到,既然我們能拿到請求資料和響應資料,我們就當然可以在其返回之前做些自己的特定需求定製,這也就是我們自定義ConverterFactory的思路。

(2)自定義一個ConverterFactory

分析完GsonConverterFactory之後,讓我們來定義一個我們自己的ConverterFactory,我們的需求是對伺服器返回的資料先進行解密操作再轉換成我們需要的bean,這是我們自己定義的CryptConverterFactory類:

public final class CryptConverterFactory extends Converter.Factory{

    public static CryptConverterFactory create() {
        return create(new Gson());
    }

    @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
    public static CryptConverterFactory create(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        return new CryptConverterFactory(gson);
    }

    private final Gson gson;

    private CryptConverterFactory(Gson gson) {
        this.gson = gson;
    }

    @Nullable
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        //@body註解時,會呼叫此方法
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new MyCryptRequestConverter(gson);
    }

    @Nullable
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if(type == CryptDetailBean.class){
            return new MyCryptRespoceConverter(gson, type);
        }
        //自己不能處理的型別交給後面的ConverterFactory處理
        return null;
    }
}

寫法是不是非常熟悉,跟GsonConverterFactory的程式碼大同小異,在這裡我就直接使用了Gson,避免重複造輪子嘛,注意如果遇到我們不能處理的型別記得返回null,交給後面的ConverterFactory進行處理。

當使用@Body註解時,就會呼叫上面的requestBodyConverter方法,這是所需要的MyCryptRequestConverter類:

final class MyCryptRequestConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private final Gson gson;

    MyCryptRequestConverter(Gson gson) {
        this.gson = gson;
    }

    @Override public RequestBody convert(T value) throws IOException {
        String request = gson.toJson(value);
        //我們可以根據自己的需求,對於request進行一些操作
        return RequestBody.create(MEDIA_TYPE,request);
    }
}

程式碼不多,我們可以看到,MyCryptRequestConverter類的作用就是將請求資料轉換為RequestBody物件,其實在這裡我們既然知道了請求的資訊request,我們當然就可以對於request做些想要的操作,例如加密等等操作。

下面這是我們的MyCryptRespoceConverter類:

final class MyCryptRespoceConverter<T> implements Converter<ResponseBody, 
            
           

相關推薦

Android 詳細Retrofit2.0基本使用總結

一、前言 今天主要是對於網路請求框架Retrofit2.0做一次總結,並通過具體例項展示一下Retrofit2.0的使用方法。 二、概述 Retrofit2.0是一款基於OkHttp的非常適用於RESTful URL格式的HTTP網路請求框架。OkHt

Android 網路請求框架 Retrofit2.0實踐使用總結

比較AsyncHttpClient 、Volley、Retrofit三者的請求時間 使用 單次請求 7個請求 25個請求 AsyncHttpClient 941ms 4539ms 13957ms Volley

詳細的 Matplotlib入門指導

數字 plt grid 而不是 一個 標記 src 圖片 out hMatplotlib是最受歡迎的二維圖形庫,但有時我們很難做到得心應手的去使用。 如何更改圖例上的標簽名稱? 如何設置刻度線? 如何將比例更改為對數? 如何在我的情節中添加註釋和箭頭? 如何在我的圖中添加網

Retrofit2.0 基本使用

相關文章: Retrofit2.0 // Retrofit庫 implementation 'com.squareup.retrofit2:retrofit:2.0.2' // Okhttp庫 implementation 'com.s

詳細的大資料學習路線圖

  Hadoop大資料學習線路圖 入門知識 對於我們新手入門學習hadoop的朋友來說,首先了解一下雲端計算和雲端計算技術是有必要的。下面先是介紹雲端計算和雲端計算技術的: 雲端計算,是一種基於網際網路的計算方式,通過這種方式,共享的軟硬體資源和資訊可以按需求提供給計算機

自學大資料需要從哪裡入手 收藏了詳細的學習路線圖

近期,經常聽到這樣一句特別豪氣的話"我家裡有礦"!對於資料而言,沒有大資料技術的資料一無是處,但經過大資料技術處理的資料,就是金礦,價值連城! 面臨能將"礦"玩弄於股掌之間的大資料技術,誰能坐懷不亂?誰又能忍心放棄這個難得的機遇呢?那麼問題來了,該如何學習大資料技術呢?學習

Retrofit2.0使用總結及注意事項

概述 隨著Google對HttpClient 摒棄,和Volley的逐漸沒落,OkHttp開始異軍突起,而Retrofit則對okHttp進行了強制依賴。 Retrofit是由Square公司出品的針對於Android和Java的型別安全的Http客戶端,

網路請求框架之Retrofit2.0基本講解

Retrofit2.0簡介   Retrofit是一套RESTful架構的Android(Java)客戶端實現,基於註解,提供JSON to POJO(Plain Ordinary Java Object,簡單Java物件),POJO to JSON,網路請求

Android() activity、intent知識和總結

導讀: 第一部分,基礎activity知識 第二部分,Intent傳遞值 第三部分,Intent傳遞類物件 Activity 1.建立Activity的注意點

Android框架之路——Retrofit2.0的初窺(包含Gson)

參考部落格: 實現效果:        使用姿勢:    1. 使用教程 新增依賴 compile ‘com.squareup.retrofit2:retrofit:2.2.0’ compile ‘com.squareup.retr

你知道如何計算CNN感受野嗎?這裡有詳細指南

   作者: 葉  虎 編輯:趙一帆前  言本文翻譯自A guide to receptive

給你詳細的 Spring Boot 知識清單

在過去兩三年的Spring生態圈,最讓人興奮的莫過於Spring Boot框架。或許從命名上就能

2021來了,小菜雞的2020總結

  2020年過去了,一切如此的不平凡卻又如此的波瀾不驚,不平凡之處始於一場突如其來的疫情,致使很多人經歷了生離死別,無數的無名英雄替我們在最危難處砥礪前行;而波瀾不驚之處在於有了這些人的付出,我個人的生活並未受到太大起伏,剛開始的職業生涯也算是有條不紊,龜速又堅定的向前,主要我個人算是一個死宅,除了打球,沒

Android:這是非常詳細的MVP+Rxjava2.0+Retrofit2.0相結合舉例RecyclerView的實戰篇章

MVP+Rxjava2.0+Retrofit2.0現在是非常火的組合 MVP相信大家已經在各大網站和各大佬的文章中已經瞭解很多理論的理解了 MVP其實就是M層請求資料 在P層裡進行M層和V層的互動 V層得到資料後展示資料 比如說豺狼媽媽去捕食 捕到食物後

超級詳細的Vue-cli3.0使用教程[趕緊來試試!]

前言 在vue-cli 2.X的時候,也寫過一篇類似的文章,在八月份的時候vue-cli已經更新到了3.X,新版本的腳手架,功能灰常強大,試用過後非常喜歡,寫篇教程來幫助各位踩一下坑。 游泳、健身瞭解一下:部落格、前端積累文件、公眾號、GitHub 主要內容: 零配置啟動/打包一個.v

Android RxJava:這是全面 & 詳細 的RxJava操作符 使用攻略

本人工作不忙的時候就喜歡瀏覽一些文章,我在這裡把一些覺得不錯的文章分享給大家,希望我能幫助到你們! 前言 Rxjava,由於其基於事件流的鏈式呼叫、邏輯簡潔 & 使用簡單的特點,深受各大 Android開發者的歡迎。 Github截圖 RxJav

這是詳細的 Retrofit 2.0 使用教程(含例項講解)

前言 在Andrroid開發中,網路請求十分常用 而在Android網路請求庫中,Retrofit是當下最熱的一個網路請求庫 今天,我將獻上一份非常詳細Retrofit v2.0的使用教程,希望你們會喜歡。 目錄 1. 簡介

Android:這是全面 & 詳細的Webview使用攻略

2. 作用顯示和渲染Web頁面直接使用html檔案(網路上或本地assets中)作佈局可和JavaScript互動呼叫WebView控制元件功能強大,除了具有一般View的屬性和設定外,還可以對url請求、頁面載入、渲染、頁面互動進行強大的處理。3. 使用介紹一般來說Webview可單獨使用,可聯合其工具類一

Android:這是詳細的Socket使用攻略

前言Socket的使用在 Android網路程式設計中非常重要今天我將帶大家全面瞭解 Socket 及 其使用方法 目錄1.網路基礎1.1 計算機網路分層計算機網路分為五層:物理層、資料鏈路層、網路層、運輸層、應用層其中:網路層:負責根據IP找到目的地址的主機運輸層:通過埠把資料傳到目的主機的目的程序,來實現

【約稿】給自己交年度總結——我的2014年

pos 我的2014 ora sans ext 經歷 簡單介紹 分享 part 主題:我的2014年(12月29日截稿) 一眨眼的功夫2014年就過去了。這一年對非常多人來說或許是不平靜的一年,或許是收獲的一年,或許是“黎明前黑暗”的一年