1. 程式人生 > >Android Retrofit2框架的使用,以及解析複雜Json(其實也不算太複雜)

Android Retrofit2框架的使用,以及解析複雜Json(其實也不算太複雜)

 

Retrofit是Square公司的開源專案,是基於OKHttp進行的應用層封裝

Retrofit官方文件:http://square.github.io/retrofit/

Retrofit GitHub地址:https://github.com/square/retrofit

如何使用Retrofit?

1、引入Retrofit2依賴庫

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    // OkHttp
    implementation 'com.squareup.okhttp3:okhttp:3.12.0'

2、當然不能忘記 AndroidMainfest.xml 中的網路許可權

    // 網路許可權
    <uses-permission android:name="android.permission.INTERNET" />

3、請求介面,SoJson的免費農曆查詢API介面:https://www.sojson.com/api/lunar.html該介面只支援GET請求

如:https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1

返回json:

{
	"status": 200,
	"message": "success",
	"data": {
		"year": 2018,
		"month": 10,
		"day": 1,
		"lunarYear": 2018,
		"lunarMonth": 8,
		"lunarDay": 22,
		"cnyear": "貳零壹捌 ",
		"cnmonth": "八",
		"cnday": "廿二",
		"hyear": "戊戌",
		"cyclicalYear": "戊戌",
		"cyclicalMonth": "辛酉",
		"cyclicalDay": "丙寅",
		"suit": "祭祀,冠笄,會親友,拆卸,起基,除服,成服,移柩,啟鑽,安葬,沐浴,捕捉,開光,塑繪",
		"taboo": "作灶,祭祀,入宅,嫁娶",
		"animal": "狗",
		"week": "Monday",
		"festivalList": ["國慶節"],
		"jieqi": {
			"9": "寒露",
			"24": "霜降"
		},
		"maxDayInMonth": 29,
		"leap": false,
		"lunarYearString": "戊戌",
		"bigMonth": false
	}
}

 

Retrofit第一步:建立網路請求介面 HttpService.java

public interface HttpService {
    
    /**
     * GET請求,合併後URL為:https://www.sojson.com/open/api/lunar/json.shtml?date=xxx
     * @param date 日期引數
     * @return 返回值Call,泛型ResponseBody
     */
    @GET("/open/api/lunar/json.shtml")
    Call<ResponseBody> setGETParameter(@Query("date") String date);

}

Retrofit第二步:在呼叫網路請求的程式碼中(如 MainActivity.java),建立Retrofit物件,建立介面物件,設定網路請求引數放回Call物件

        // 建立Retrofit
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl("https://www.sojson.com/")
                .build();

        // 建立服務介面
        HttpService service = retrofit.create(HttpService.class);

        // 設定引數,返回Call物件
        final Call<ResponseBody> call = service.setGETParameter("2018-10-1");

Retrofit第三步:執行網路請求

同步請求方式:不能再主執行緒中執行,否則將丟擲:android.os.NetworkOnMainThreadException

        // 同步方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response<ResponseBody> responseBody = call.execute();
                    Log.e("my_info", responseBody.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

非同步請求方式:通過回撥的方式處理請求結果

        // 非同步方式
        call.enqueue(new Callback<ResponseBody>() {

            // 成功回撥
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.e("my_info", response.toString());
            }

            // 失敗回撥
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.e("my_info", t.toString());
            }
        });

經過這幾步之後,就完成Retrofit的網路請求了,請求結果Log如下:

E/my_info: Response{protocol=h2, code=403, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}

問題來了,響應碼為啥不是200,而是403?

百度:403錯誤是網站訪問過程中,常見的錯誤提示。資源不可用。伺服器理解客戶的請求,但拒絕處理它。通常由於伺服器上檔案或目錄的許可權設定導致,比如IIS或者apache設定了訪問許可權不當。一般會出現以下提示:403錯誤。關閉了IE的"顯示友好的HTTP錯誤",顯示沒有許可權訪問。

我:伺服器有保護機制,拒絕了你的請求。

此時,請求結果的response物件的body是空的,怎樣才能好好get資料呢?正確姿勢如下:

加入請求頭,偽裝成Chrome瀏覽器,通過OKHttp的攔截器的方式加入請求頭,OKHttp的攔截器又分為應用攔截器和網路攔截器(後面有時間再開篇幅),這裡使用的是應用攔截器

在建立Retrofit物件前建立OkHttpClient物件,並通過addInterceptor方法設定應用攔截器

        // 建立OkHttpClient.Builder物件
        OkHttpClient.Builder builder = new OkHttpClient().newBuilder() ;
        // 設定攔截器
        builder.addInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                // 設定Header
                Request newRequest = chain.request().newBuilder()
                        .removeHeader("User-Agent")
                        .addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36")
                        .build() ;
                return chain.proceed(newRequest);
            }
        }) ;
        // 獲取OkHttpClient物件
        OkHttpClient client = builder.build();

修改建立Retrofit物件的程式碼,設定Client為上面建立的OkHttpClient物件:

        // 建立Retrofit
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl("https://www.sojson.com/")
                .client(client)
                .build();

重新執行,結果Log如下:

E/my_info: Response{protocol=h2, code=200, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}

這時候我們可以輸出一下請求回來的資料,在請求成功的回撥中加入Log:

            // 成功回撥
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.e("my_info", response.toString());
                try {
                    Log.e("my_info", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

結果:

E/my_info: Response{protocol=h2, code=200, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}
    {"status":200,"message":"success","data":{"year":2018,"month":10,"day":1,"lunarYear":2018,"lunarMonth":8,"lunarDay":22,"cnyear":"貳零壹捌 ","cnmonth":"八","cnday":"廿二","hyear":"戊戌","cyclicalYear":"戊戌","cyclicalMonth":"辛酉","cyclicalDay":"丙寅","suit":"祭祀,冠笄,會親友,拆卸,起基,除服,成服,移柩,啟鑽,安葬,沐浴,捕捉,開光,塑繪","taboo":"作灶,祭祀,入宅,嫁娶","animal":"狗","week":"Monday","festivalList":["國慶節"],"jieqi":{"9":"寒露","24":"霜降"},"maxDayInMonth":29,"leap":false,"lunarYearString":"戊戌","bigMonth":false}}

上面的是一個簡單的GET請求,下面在看看POST如何進行:

在請求介面 HttpService.java 中增加POST介面配置,宣告註解為@POST 和 @FormUrlEncoded,引數註解為@Field,程式碼:

public interface HttpService {

    /**
     * GET請求,合併後URL為:https://www.sojson.com/open/api/lunar/json.shtml?date=xxx
     * @param date 日期引數
     * @return 返回值Call,泛型ResponseBody
     */
    @GET("/open/api/lunar/json.shtml")
    Call<ResponseBody> setGETParameter(@Query("date") String date);

    /**
     * POST請求
     */
    @POST("/open/api/lunar/json.shtml")
    @FormUrlEncoded
    Call<ResponseBody> setPOSTParameter(@Field("date") String date);

}

如果預設 @FormUrlEncoded將丟擲以下異常:

java.lang.IllegalArgumentException: @Field parameters can only be used with form encoding. (parameter #1)

設定網路請求引數放回Call物件的時候,呼叫setPOSTParameter設定引數即可

        // 設定引數,返回Call物件
//        final Call<ResponseBody> call = service.setGETParameter("2018-10-1");
        final Call<ResponseBody> call = service.setPOSTParameter("2018-10-1");

其他和GET方式一致,因為sojson的農曆介面不支援POST請求,這裡就點到為止了

E/my_info: Response{protocol=h2, code=405, message=, url=https://www.sojson.com/open/api/lunar/json.shtml}

------------------------------------------------------------------華麗的分割線,不要問我華麗是誰------------------------------------------------------------------

上面僅僅是簡單的請求,又怎能滿足各種刁鑽的開發需求呢,Retrofit提供了json的解析器,對請求返回的json字串進行自動解析封裝。

首先引入依賴:

    // Retrofit的GSON解析
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

JSON轉實體類,將返回的JSON資料格式,封裝成Bean實體類,可以看到上面的JSON比較長,資料也比一般格式複雜,作為優秀的碼農是不會一個個字元手敲的!!使用Android Studio的GsonFormat外掛可以快速的幫生成程式碼,至於用法,這裡就不介紹了。

JSON分析:因為上面返回的

        "jieqi": {
			"9": "寒露",
			"24": "霜降"
		},

JSON內容的鍵與值是不確定的,對於少量的可能,我們可以在實體物件當中窮舉,但是對於上面的窮舉似乎沒有人會去做吧.....

解決方法:將這個帶不確定性的欄位,封裝成JsonObject型別,後續拿到Bean實體物件的時候再進行Json遍歷處理。(如有更好的方案歡迎指教,感激不盡)

最終調整的Bean實體程式碼如下 Lunar.java

public class Lunar{

    private int status;
    private String message;
    private DataBean data;

    // 此處省略set、get和toString程式碼

    public static class DataBean {

        private int year;
        private int month;
        private int day;
        private int lunarYear;
        private int lunarMonth;
        private int lunarDay;
        private String cnyear;
        private String cnmonth;
        private String cnday;
        private String hyear;
        private String cyclicalYear;
        private String cyclicalMonth;
        private String cyclicalDay;
        private String suit;
        private String taboo;
        private String animal;
        private String week;
        private JsonObject jieqi;
        private int maxDayInMonth;
        private boolean leap;
        private String lunarYearString;
        private boolean bigMonth;
        private List<String> festivalList;

        // 此處省略set、get和toString程式碼
}

將請求介面 HttpService.java 和 請求呼叫Call的泛型 ResponseBody 改成 Lunar

//    /**
//     * GET請求,合併後URL為:https://www.sojson.com/open/api/lunar/json.shtml?date=xxx
//     * @param date 日期引數
//     * @return 返回值Call,泛型ResponseBody
//     */
//    @GET("/open/api/lunar/json.shtml")
//    Call<ResponseBody> setGETParameter(@Query("date") String date);
    @GET("/open/api/lunar/json.shtml")
    Call<Lunar> setGETParameter(@Query("date") String date);
        // 設定引數,返回Call物件
//        final Call<ResponseBody> call = service.setGETParameter("2018-10-1");
//        final Call<ResponseBody> call = service.setPOSTParameter("2018-10-1");
        final Call<Lunar> call = service.setGETParameter("2018-10-1");

執行請求程式碼也一樣需要修改:

        //非同步方式
        call.enqueue(new Callback<Lunar>() {

            // 成功回撥
            @Override
            public void onResponse(Call<Lunar> call, Response<Lunar> response) {
                Log.e("my_info", response.toString());
                Lunar lunar = response.body();
                Log.e("my_info", lunar.toString());
            }

            //失敗回撥
            @Override
            public void onFailure(Call<Lunar> call, Throwable t) {
                Log.e("my_info", t.toString());
            }
        });

不要忘記在建立Retrofit物件的時候加入解析工廠:

        // 建立Retrofit
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl("https://www.sojson.com/")
                .client(client)
                // Gson解析工廠
                .addConverterFactory(GsonConverterFactory.create())
                .build();

再次執行,結果Log如下:

E/my_info: Response{protocol=h2, code=200, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}
    Lunar{status=200, message='success', data=DataBean{year=2018, month=10, day=1, lunarYear=2018, lunarMonth=8, lunarDay=22, cnyear='貳零壹捌 ', cnmonth='八', cnday='廿二', hyear='戊戌', cyclicalYear='戊戌', cyclicalMonth='辛酉', cyclicalDay='丙寅', suit='祭祀,冠笄,會親友,拆卸,起基,除服,成服,移柩,啟鑽,安葬,沐浴,捕捉,開光,塑繪', taboo='作灶,祭祀,入宅,嫁娶', animal='狗', week='Monday', jieqi={"9":"寒露","24":"霜降"}, maxDayInMonth=29, leap=false, lunarYearString='戊戌', bigMonth=false, festivalList=[國慶節]}}

Retrofit2的使用就到這裡了,後面有時間將會對Retrofit + OKHttp + RxJava進行封裝做篇幅,謝謝~

附上:原始碼