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进行封装做篇幅,谢谢~

附上:源代码

猜你喜欢

转载自blog.csdn.net/c_3822/article/details/84593983
今日推荐