聊聊 Android 的网络请求框架 Retrofit 2 + okhttp 3

Palingenesis 2019-06-26

工作中跟客户端沟通时产生了点问题, 记录一下.

起因

后端有一个接口是这样的

@RequestMapping("android")
public BaseResult android(@RequestBody RequestData requestData) {
    
}

RequestData.java

package com.junbaor.network.model;

public class RequestData {
    private Integer id;
    private String name;
    // 省略 get set
}

如果使用 http://localhost:8080/android?id=1&name=张三 是调不通的

聊聊 Android 的网络请求框架 Retrofit 2 + okhttp 3

只能处理 Content-Type 为 application/json 的请求, 需要把参数放在 post 请求体内

{
    "id":1,
    "name":"张三"
}

聊聊 Android 的网络请求框架 Retrofit 2 + okhttp 3

问题

客户端使用第一种方式调不通, 告知需把参数转成 json 对象放到请求体内,
反馈说之前都是按照第一种方式调用的接口, 做不到第二种。

解决

后端没有找到优雅的解决方式, 被迫修改接口实现, 之所以继续保留 requestData 是为了兼容其它调用方.

@RequestMapping(value = "android")
public BaseResult android(@RequestBody(required = false) RequestData requestData,
                          @RequestParam(required = false) Integer id,
                          @RequestParam(required = false) String name) {
    // 处理逻辑时先判断 requestParam 是否有值, 没有的话再从 requestData 取
}

研究 Android

long long ago 写过一点 Android , 不太相信想自己试试。
打听后得知客户端的网络框架是 Retrofit 配合 OkHttp。
开始研究 Android, 安装环境就不说了。

引包

先引入客户端使用的网络框架, 使用的都是最新版, 由于数据是 json 格式再引入 gson 库(虽然不知道他们用的是什么解析库). converter-gson 是 retrofit2 的工具包, 用来把 json 封装成对象.

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.9.1'
compile 'com.google.code.gson:gson:2.8.2'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

编码

// OkHttp 请求日志拦截器
HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
});
logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

// OkHttp 客户端
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
        .connectTimeout(2, TimeUnit.MILLISECONDS)
        .addNetworkInterceptor(logInterceptor)
        .build();
        
// 把后台返回的时间戳转成 java.util.Date
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new GsonDateTypeAdapter()).create();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://127.0.0.1:8080")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();
        
 //请求参数
RequestData requestData = new RequestData();
requestData.setId(1);
requestData.setName("张三");

TestService testService = retrofit.create(TestService.class);
Call testServiceReslut = testService.getTestResult(requestData);

testServiceReslut.enqueue(new Callback<ResponseData>() {
    @Override
    public void onResponse(Call<ResponseData> call, Response<ResponseData> response) {
        System.out.println("响应成功, 数据:" + response.body());
    }

    @Override
    public void onFailure(Call<ResponseData> call, Throwable t) {
        System.out.println("响应失败, 原因:" + t.getMessage());
    }
});

源码

参见:https://github.com/junbaor/an...
Android 端重点关注:com.junbaor.network.NetworkTest
Server 端重点关注:com.junbaor.network.NetworkApplication

花絮

Gson 时间戳转 Date

GsonDateTypeAdapter.java

package com.junbaor.network.extend;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.util.Date;

/**
 * Created by junbaor on 2017/12/13.
 *
 * 将后台返回的时间戳转成 java.util.Date
 * 参见: https://stackoverflow.com/questions/41348055/gson-dateformat-to-parse-output-unix-timestamps
 */
public class GsonDateTypeAdapter extends TypeAdapter<Date> {

    @Override
    public void write(JsonWriter out, Date value) throws IOException {
        if (value == null) {
            out.nullValue();
        } else {
            out.value(value.getTime() / 1000);
        }
    }

    @Override
    public Date read(JsonReader in) throws IOException {
        if (in != null) {
            return new Date(in.nextLong() / 1000);
        } else {
            return null;
        }
    }
}

okhttp 日志打印

在网上找到了这个 https://github.com/square/okh...
由于没有 logger 实现, 打印的比较凌乱, 索性把代码拷到项目中以便修改, 用控制台输出实现了 logger 接口

// OkHttp 日志拦截器
HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
});
logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

日志效果:

--> POST http://127.0.0.1:8080/android http/1.1
Content-Type: application/json; charset=UTF-8
Content-Length: 24
Host: 127.0.0.1:8080
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.9.1

{"id":1,"name":"张三"}
--> END POST (24-byte body)
<-- 200 http://127.0.0.1:8080/android (15ms)
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 13 Dec 2017 16:05:49 GMT

{"code":200,"message":"成功","data":{"id":1,"name":"张三","birthday":1513181149729}}
<-- END HTTP (88-byte body)
响应成功, 数据:BaseResult{code=200, message='成功', data=ResponseData{id=1, name='张三', birthday=Sun Jan 18 20:19:41 CST 1970}}

相关推荐