主要对象和概念

1. OkHttpClient

HTTP 请求的入口点,建议作为单例使用(线程安全且资源高效)

// 创建默认配置的客户端
OkHttpClient client = new OkHttpClient();
 
// 自定义配置的客户端
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .cache(new Cache(cacheDirectory, cacheSize))
    .addInterceptor(new LoggingInterceptor())
    .addNetworkInterceptor(new StethoInterceptor())
    .build();

2. Request

表示 HTTP 请求,包含 URL、方法、头部和请求体

// GET 请求
Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .header("Authorization", "Bearer token123")
    .build();
 
// POST 请求(带 JSON 数据)
MediaType JSON = MediaType.get("application/json; charset=utf-8");
String json = "{\"name\":\"John\", \"age\":30}";
RequestBody body = RequestBody.create(json, JSON);
 
Request request = new Request.Builder()
    .url("https://api.example.com/users")
    .post(body)
    .build();

3. Call

表示一个准备执行的请求,可以同步或异步执行

// 同步请求
try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
        String responseData = response.body().string();
        // 处理响应
    }
}
 
// 异步请求
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理失败
    }
    
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            String responseData = response.body().string();
            // 处理响应(注意:不在主线程)
        }
    }
});

4. Response

表示 HTTP 响应,包含状态码、头部和响应体

try (Response response = client.newCall(request).execute()) {
    // 状态信息
    int statusCode = response.code();
    String message = response.message();
    
    // 头部信息
    String contentType = response.header("Content-Type");
    Headers headers = response.headers();
    
    // 响应体
    ResponseBody body = response.body();
    String stringBody = body.string(); // 只能调用一次
    byte[] bytes = body.bytes(); // 只能调用一次
    InputStream stream = body.byteStream(); // 可多次读取
}

<!> 注意 确保在使用后关闭 Response 以避免资源泄漏

5. Interceptor

拦截器,用于在请求发送前和响应接收后添加自定义逻辑

// 日志拦截器
public class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        
        long startTime = System.nanoTime();
        Log.d("OkHttp", String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));
        
        Response response = chain.proceed(request);
        
        long endTime = System.nanoTime();
        Log.d("OkHttp", String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (endTime - startTime) / 1e6d, response.headers()));
        
        return response;
    }
}

常用功能

1. 表单提交

RequestBody formBody = new FormBody.Builder()
    .add("username", "john")
    .add("password", "secret")
    .build();
 
Request request = new Request.Builder()
    .url("https://api.example.com/login")
    .post(formBody)
    .build();

2. 文件上传

MediaType mediaType = MediaType.parse("image/jpeg");
File file = new File("path/to/image.jpg");
RequestBody requestBody = RequestBody.create(file, mediaType);
 
Request request = new Request.Builder()
    .url("https://api.example.com/upload")
    .post(requestBody)
    .build();

3. 多部分请求 Multipart

MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
RequestBody requestBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("title", "My Image")
    .addFormDataPart("image", "image.png", RequestBody.create(new File("path/to/image.png"), MEDIA_TYPE_PNG))
    .build();
 
Request request = new Request.Builder()
    .url("https://api.example.com/upload")
    .post(requestBody)
    .build();

4. 超时设置

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)    // 连接超时
    .readTimeout(30, TimeUnit.SECONDS)        // 读取超时
    .writeTimeout(30, TimeUnit.SECONDS)      // 写入超时
    .callTimeout(60, TimeUnit.SECONDS)       // 整个调用超时
    .build();

5. 缓存配置

// 设置缓存(10MB)
int cacheSize = 10 * 1024 * 1024; // 10 MB
File cacheDirectory = new File(context.getCacheDir(), "http-cache");
Cache cache = new Cache(cacheDirectory, cacheSize);
 
OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();
// 使用持久化 CookieJar
CookieJar cookieJar = new PersistentCookieJar(
    new SetCookieCache(), 
    new SharedPrefsCookiePersistor(context)
);
 
OkHttpClient client = new OkHttpClient.Builder()
    .cookieJar(cookieJar)
    .build();

高级特性

1. 连接池管理

ConnectionPool connectionPool = new ConnectionPool(5, 5, TimeUnit.MINUTES);
OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build();

2. 证书 pinning

// 只信任指定证书
String hostname = "publicobject.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build();
 
OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build();

3. 代理设置

Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080));
OkHttpClient client = new OkHttpClient.Builder()
    .proxy(proxy)
    .build();

4. 认证

// 基本认证
Authenticator authenticator = new Authenticator() {
    @Override
    public Request authenticate(Route route, Response response) throws IOException {
        String credential = Credentials.basic("username", "password");
        return response.request().newBuilder()
            .header("Authorization", credential)
            .build();
    }
};
 
OkHttpClient client = new OkHttpClient.Builder()
    .authenticator(authenticator)
    .build();

错误处理

1. 网络错误

try {
    Response response = client.newCall(request).execute();
    // 处理响应
} catch (IOException e) {
    // 网络连接问题
    if (e instanceof SocketTimeoutException) {
        // 超时错误
    } else if (e instanceof UnknownHostException) {
        // DNS解析错误
    } else {
        // 其他网络错误
    }
}

2. HTTP 状态码错误

Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
    switch (response.code()) {
        case 400:
            // 错误请求
            break;
        case 401:
            // 未授权
            break;
        case 404:
            // 未找到
            break;
        case 500:
            // 服务器错误
            break;
        default:
            // 其他HTTP错误
    }
}