首页
/ OkHttp实战指南:从基础使用到高级特性

OkHttp实战指南:从基础使用到高级特性

2026-02-04 04:42:26作者:牧宁李

本文全面介绍了OkHttp网络库的核心功能和使用技巧,涵盖了同步与异步请求的基本用法、请求构建器与响应处理方法、文件上传下载与流式处理,以及超时控制与重试机制配置。通过详细的代码示例和最佳实践,帮助开发者从基础入门到高级特性掌握OkHttp的使用,提升网络请求的效率和可靠性。

同步与异步请求的基本用法

OkHttp提供了两种主要的请求执行方式:同步请求和异步请求。这两种方式各有其适用场景,理解它们的区别和使用方法对于构建高效的网络应用至关重要。

同步请求:阻塞式执行

同步请求是最直接的执行方式,调用线程会阻塞直到请求完成并返回响应。这种方式简单直观,适合在后台线程或需要等待结果的场景中使用。

Java同步请求示例

OkHttpClient client = new OkHttpClient();

public String fetchDataSync(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
        if (!response.isSuccessful()) {
            throw new IOException("请求失败,状态码: " + response.code());
        }
        return response.body().string();
    }
}

Kotlin同步请求示例

val client = OkHttpClient()

fun fetchDataSync(url: String): String {
    val request = Request.Builder()
        .url(url)
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) {
            throw IOException("请求失败,状态码: ${response.code}")
        }
        return response.body.string()
    }
}

同步请求的执行流程可以用以下流程图表示:

flowchart TD
    A[创建Request对象] --> B[调用execute方法]
    B --> C[线程阻塞等待]
    C --> D{请求成功?}
    D -->|是| E[处理响应数据]
    D -->|否| F[抛出异常]
    E --> G[返回结果]
    F --> G

异步请求:非阻塞式执行

异步请求通过回调机制实现非阻塞执行,调用线程不会被阻塞,请求结果通过回调接口返回。这种方式适合在主线程或需要避免阻塞的场景中使用。

Java异步请求示例

OkHttpClient client = new OkHttpClient();

public void fetchDataAsync(String url) {
    Request request = new Request.Builder()
        .url(url)
        .build();

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            // 处理请求失败
            e.printStackTrace();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
                if (!response.isSuccessful()) {
                    throw new IOException("请求失败,状态码: " + response.code());
                }
                String responseData = responseBody.string();
                // 处理响应数据
                System.out.println(responseData);
            }
        }
    });
}

Kotlin异步请求示例

val client = OkHttpClient()

fun fetchDataAsync(url: String) {
    val request = Request.Builder()
        .url(url)
        .build()

    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            // 处理请求失败
            e.printStackTrace()
        }

        override fun onResponse(call: Call, response: Response) {
            response.use {
                if (!response.isSuccessful) {
                    throw IOException("请求失败,状态码: ${response.code}")
                }
                val responseData = response.body.string()
                // 处理响应数据
                println(responseData)
            }
        }
    })
}

异步请求的执行流程如下:

sequenceDiagram
    participant MainThread
    participant OkHttp
    participant BackgroundThread
    participant Callback

    MainThread->>OkHttp: enqueue(request)
    OkHttp->>BackgroundThread: 执行网络请求
    BackgroundThread->>Callback: onResponse/onFailure
    Callback->>MainThread: 回调处理结果

两种方式的对比与选择

为了帮助开发者更好地选择适合的请求方式,以下是同步和异步请求的详细对比:

特性 同步请求 异步请求
执行线程 调用线程阻塞 后台线程执行
响应方式 直接返回Response对象 通过回调接口返回
适用场景 后台线程、需要等待结果的场景 主线程、避免阻塞的场景
错误处理 通过异常抛出 通过onFailure回调
资源管理 需要手动关闭Response 在回调中自动管理
性能影响 可能阻塞调用线程 不会阻塞调用线程

选择建议

  1. 使用同步请求的场景

    • 在后台线程中执行网络操作
    • 需要等待请求结果才能继续执行的逻辑
    • 批量处理多个顺序相关的请求
  2. 使用异步请求的场景

    • 在Android主线程中执行网络操作
    • 需要保持UI响应的场景
    • 并行处理多个独立请求

最佳实践与注意事项

资源管理

无论是同步还是异步请求,都需要妥善管理响应资源:

// 正确的资源管理方式
try (Response response = client.newCall(request).execute()) {
    // 处理响应
    String data = response.body().string();
    // 使用数据
}

错误处理

完善的错误处理机制对于网络请求至关重要:

client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        when {
            e is SocketTimeoutException -> {
                // 处理超时错误
                Log.e("Network", "请求超时")
            }
            e is ConnectException -> {
                // 处理连接错误
                Log.e("Network", "连接失败")
            }
            else -> {
                // 其他网络错误
                Log.e("Network", "网络错误: ${e.message}")
            }
        }
    }

    override fun onResponse(call: Call, response: Response) {
        response.use {
            if (response.isSuccessful) {
                // 处理成功响应
                val data = response.body.string()
                processData(data)
            } else {
                // 处理HTTP错误状态码
                Log.e("Network", "HTTP错误: ${response.code}")
            }
        }
    }
})

线程安全考虑

在使用异步请求时,需要注意线程安全问题:

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // 注意:这个回调在后台线程执行
        // 如果需要更新UI,需要切换到主线程
        runOnUiThread(() -> {
            updateUI(response.body().string());
        });
    }
    
    @Override
    public void onFailure(Call call, IOException e) {
        // 同样需要在主线程处理UI更新
        runOnUiThread(() -> {
            showError(e.getMessage());
        });
    }
});

通过合理选择同步或异步请求方式,并结合适当的错误处理和资源管理策略,可以构建出既高效又稳定的网络请求功能。

请求构建器与响应处理方法

OkHttp的请求构建器和响应处理是其核心功能之一,提供了灵活且强大的API来构建HTTP请求和处理服务器响应。通过流畅的构建器模式,开发者可以轻松地配置各种请求参数,而响应处理则提供了多种方式来读取和解析返回的数据。

Request.Builder:灵活的请求构建

OkHttp使用构建器模式来创建HTTP请求,Request.Builder类提供了丰富的方法来配置请求的各个方面。下面是一个完整的请求构建示例:

// 创建基本的GET请求
Request getRequest = new Request.Builder()
    .url("https://api.example.com/users")
    .get()
    .build();

// 创建带参数的POST请求
MediaType JSON = MediaType.get("application/json; charset=utf-8");
String jsonBody = "{\"name\":\"John\", \"age\":30}";
RequestBody requestBody = RequestBody.create(jsonBody, JSON);

Request postRequest = new Request.Builder()
    .url("https://api.example.com/users")
    .post(requestBody)
    .addHeader("Authorization", "Bearer token123")
    .addHeader("Content-Type", "application/json")
    .build();

// 创建带查询参数的请求
HttpUrl url = HttpUrl.parse("https://api.example.com/search")
    .newBuilder()
    .addQueryParameter("q", "okhttp")
    .addQueryParameter("page", "1")
    .addQueryParameter("limit", "10")
    .build();

Request searchRequest = new Request.Builder()
    .url(url)
    .get()
    .build();

请求构建器的主要方法

方法 描述 示例
.url() 设置请求URL .url("https://example.com")
.get() 设置GET方法 .get()
.post() 设置POST方法并添加请求体 .post(requestBody)
.put() 设置PUT方法 .put(requestBody)
.delete() 设置DELETE方法 .delete()
.head() 设置HEAD方法 .head()
.patch() 设置PATCH方法 .patch(requestBody)
.addHeader() 添加请求头 .addHeader("Accept", "application/json")
.header() 设置请求头(覆盖同名头) .header("User-Agent", "MyApp")
.removeHeader() 移除请求头 .removeHeader("Cache-Control")

请求体(RequestBody)的类型

OkHttp支持多种类型的请求体,适用于不同的数据格式:

// 1. 字符串请求体
RequestBody stringBody = RequestBody.create("plain text", MediaType.get("text/plain"));

// 2. JSON请求体
String json = "{\"key\":\"value\"}";
RequestBody jsonBody = RequestBody.create(json, MediaType.get("application/json"));

// 3. 表单数据
RequestBody formBody = new FormBody.Builder()
    .add("username", "john")
    .add("password", "secret")
    .build();

// 4. 多部分表单数据(文件上传)
RequestBody multipartBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("title", "My File")
    .addFormDataPart("file", "file.txt",
        RequestBody.create(new File("path/to/file.txt"), MediaType.get("text/plain")))
    .build();

// 5. 流式请求体
RequestBody streamingBody = new RequestBody() {
    @Override
    public MediaType contentType() {
        return MediaType.get("application/octet-stream");
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        // 流式写入数据
        sink.writeUtf8("Streaming data...");
    }
};

响应处理:多种数据读取方式

OkHttp的Response对象提供了多种处理响应数据的方法:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

try (Response response = client.newCall(request).execute()) {
    // 检查响应状态
    if (!response.isSuccessful()) {
        throw new IOException("Unexpected code: " + response);
    }

    // 方法1:以字符串形式读取响应体
    String responseString = response.body().string();
    System.out.println("Response as string: " + responseString);

    // 方法2:以字节流形式读取
    byte[] responseBytes = response.body().bytes();
    
    // 方法3:使用流式处理(适用于大文件)
    try (InputStream inputStream = response.body().byteStream()) {
        // 处理输入流
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }

    // 方法4:使用Okio的Source(高性能处理)
    try (BufferedSource source = response.body().source()) {
        while (!source.exhausted()) {
            String chunk = source.readUtf8(1024); // 每次读取1KB
            System.out.print(chunk);
        }
    }
}

响应处理的最佳实践

flowchart TD
    A[执行请求] --> B{响应是否成功?}
    B -->|是| C[处理响应体]
    B -->|否| D[处理错误]
    
    C --> E[选择读取方式]
    E --> F[小文本: string()]
    E --> G[二进制数据: bytes()]
    E --> H[大文件: byteStream()]
    E --> I[高性能: source()]
    
    F --> J[关闭响应体]
    G --> J
    H --> J
    I --> J
    
    D --> K[记录错误信息]
    K --> L[抛出异常或重试]

高级响应处理技巧

1. 响应缓存控制

Response response = client.newCall(request).execute();

// 检查缓存相关信息
if (response.cacheResponse() != null) {
    System.out.println("Response came from cache");
} else if (response.networkResponse() != null) {
    System.out.println("Response came from network");
}

// 获取缓存控制头
String cacheControl = response.header("Cache-Control");
String expires = response.header("Expires");

2. 重定向处理

// 检查重定向信息
Response response = client.newCall(request).execute();
int redirectCount = 0;
Response priorResponse = response.priorResponse();

while (priorResponse != null) {
    redirectCount++;
    priorResponse = priorResponse.priorResponse();
}

System.out.println("Number of redirects: " + redirectCount);

3. 响应头处理

Response response = client.newCall(request).execute();

// 获取所有响应头
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
    System.out.println(headers.name(i) + ": " + headers.value(i));
}

// 获取特定头信息
String contentType = response.header("Content-Type");
String contentLength = response.header("Content-Length");
String server = response.header("Server");

// 获取日期头(自动解析)
Date date = response.headers().getDate("Date");

错误处理和异常管理

try {
    Response response = client.newCall(request).execute();
    
    if (response.isSuccessful()) {
        // 处理成功响应
        String responseData = response.body().string();
        processResponseData(responseData);
    } else {
        // 处理HTTP错误状态
        handleHttpError(response.code(), response.message());
    }
    
} catch (IOException e) {
    // 处理网络IO异常
    if (e instanceof SocketTimeoutException) {
        handleTimeoutError();
    } else if (e instanceof ConnectException) {
        handleConnectionError();
    } else {
        handleGenericNetworkError(e);
    }
} finally {
    // 确保资源释放
    if (response != null) {
        response.close();
    }
}

性能优化建议

  1. 响应体复用:对于大响应,使用流式处理避免内存溢出
  2. 连接池管理:合理配置OkHttpClient的连接池参数
  3. 超时设置:根据网络状况设置合适的连接和读取超时
  4. 缓存策略:利用OkHttp的缓存机制减少网络请求
  5. GZIP压缩:OkHttp自动处理GZIP压缩,减少数据传输量

通过掌握OkHttp的请求构建和响应处理技巧,开发者可以构建出高效、可靠的网络请求逻辑,满足各种复杂的业务场景需求。

文件上传下载与流式处理

在现代应用开发中,文件的上传和下载是极其常见的需求。OkHttp提供了强大而灵活的工具来处理各种文件传输场景,从简单的文件下载到复杂的多部分表单上传,再到流式处理大文件,都能优雅地应对。

文件下载基础

OkHttp的文件下载非常简单直接。通过构建一个GET请求,我们可以获取服务器上的文件内容:

// Kotlin示例
fun downloadFile(url: String, outputFile: File) {
    val request = Request.Builder()
        .url(url)
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) throw IOException("下载失败: $response")
        
        response.body?.source()?.use { source ->
            outputFile.sink().buffer().use { sink ->
                sink.writeAll(source)
            }
        }
    }
}

// Java示例
public void downloadFile(String url, File outputFile) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
        if (!response.isSuccessful()) throw new IOException("下载失败: " + response);
        
        try (BufferedSource source = response.body().source();
             BufferedSink sink = Okio.buffer(Okio.sink(outputFile))) {
            sink.writeAll(source);
        }
    }
}

多部分文件上传

对于文件上传,OkHttp提供了MultipartBody类来处理RFC 2387标准的多部分表单数据。这是处理文件上传最常用的方式:

// 单文件上传示例
fun uploadFile(file: File, uploadUrl: String) {
    val requestBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart(
            "file", 
            file.name,
            file.asRequestBody("application/octet-stream".toMediaType())
        )
        .addFormDataPart("description", "这是一个示例文件")
        .build()

    val request = Request.Builder()
        .url(uploadUrl)
        .post(requestBody)
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) throw IOException("上传失败: $response")
        println("上传成功: ${response.body?.string()}")
    }
}

多文件与混合内容上传

OkHttp支持同时上传多个文件和文本字段,甚至可以嵌套多部分内容:

// 多文件上传示例
fun uploadMultipleFiles(files: List<File>, uploadUrl: String) {
    val multipartBuilder = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("username", "john_doe")
        .addFormDataPart("email", "john@example.com")

    files.forEachIndexed { index, file ->
        multipartBuilder.addFormDataPart(
            "file$index",
            file.name,
            file.asRequestBody("application/octet-stream".toMediaType())
        )
    }

    val request = Request.Builder()
        .url(uploadUrl)
        .post(multipartBuilder.build())
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) throw IOException("多文件上传失败")
        println("上传成功")
    }
}

流式文件处理

对于大文件,OkHttp支持流式处理,避免内存溢出问题:

flowchart TD
    A[开始文件下载] --> B[创建请求对象]
    B --> C[执行网络请求]
    C --> D{请求是否成功?}
    D -->|是| E[获取响应体Source]
    D -->|否| F[抛出异常]
    E --> G[创建文件Sink]
    G --> H[流式写入文件]
    H --> I[关闭资源]
    I --> J[下载完成]
    F --> K[错误处理]
// 流式下载大文件
fun streamDownloadLargeFile(url: String, outputFile: File, progressListener: ProgressListener) {
    val request = Request.Builder()
        .url(url)
        .build()

    val clientWithProgress = client.newBuilder()
        .addNetworkInterceptor { chain ->
            val originalResponse = chain.proceed(chain.request())
            originalResponse.newBuilder()
                .body(ProgressResponseBody(originalResponse.body!!, progressListener))
                .build()
        }
        .build()

    clientWithProgress.newCall(request).execute().use { response ->
        if (!response.isSuccessful) throw IOException("下载失败")
        
        response.body?.source()?.use { source ->
            outputFile.sink().buffer().use { sink ->
                sink.writeAll(source)
            }
        }
    }
}

// 进度监听接口
interface ProgressListener {
    fun update(bytesRead: Long, contentLength: Long, done: Boolean)
}

// 进度响应体包装器
class ProgressResponseBody(
    private val responseBody: ResponseBody,
    private val progressListener: ProgressListener
) : ResponseBody() {
    private var bufferedSource: BufferedSource? = null

    override fun contentType() = responseBody.contentType()
    override fun contentLength() = responseBody.contentLength()

    override fun source(): BufferedSource {
        if (bufferedSource == null) {
            bufferedSource = source(responseBody.source()).buffer()
        }
        return bufferedSource!!
    }

    private fun source(source: Source): Source = object : ForwardingSource(source) {
        var totalBytesRead = 0L

        override fun read(sink: Buffer, byteCount: Long): Long {
            val bytesRead = super.read(sink, byteCount)
            totalBytesRead += if (bytesRead != -1L) bytesRead else 0
            progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
            return bytesRead
        }
    }
}

高级文件上传特性

OkHttp还支持一些高级的文件上传特性:

自定义边界和内容类型

// 自定义边界和内容类型
fun uploadWithCustomBoundary(file: File, uploadUrl: String) {
    val multipartBody = MultipartBody.Builder("my-custom-boundary-123")
        .setType(MultipartBody.FORM)
        .addFormDataPart("file", file.name, file.asRequestBody())
        .addFormDataPart("timestamp", System.currentTimeMillis().toString())
        .build()

    val request = Request.Builder()
        .url(uploadUrl)
        .post(multipartBody)
        .build()

    client.newCall(request).execute().use { response ->
        // 处理响应
    }
}

带进度监控的文件上传

// 带进度监控的上传
fun uploadWithProgress(file: File, uploadUrl: String, progressListener: ProgressListener) {
    val requestBody = object : RequestBody() {
        override fun contentType() = "application/octet-stream".toMediaType()
        override fun contentLength() = file.length()

        override fun writeTo(sink: BufferedSink) {
            var uploaded = 0L
            file.source().use { source ->
                val buffer = Buffer()
                var read: Long
                while (source.read(buffer, 2048).also { read = it } != -1L) {
                    sink.write(buffer, read)
                    uploaded += read
                    progressListener.update(uploaded, contentLength(), false)
                    buffer.clear()
                }
            }
            progressListener.update(uploaded, contentLength(), true)
        }
    }

    val multipartBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("file", file.name, requestBody)
        .build()

    val request = Request.Builder()
        .url(uploadUrl)
        .post(multipartBody)
        .build()

    client.newCall(request).execute().use { response ->
        // 处理响应
    }
}

文件传输最佳实践

在实际项目中,文件上传下载需要注意以下几个最佳实践:

  1. 内存管理:对于大文件,始终使用流式处理避免内存溢出
  2. 进度反馈:为用户提供上传下载进度反馈
  3. 错误处理:妥善处理网络中断、服务器错误等情况
  4. 重试机制:为重要的文件传输实现重试逻辑
  5. 取消支持:允许用户取消长时间的文件传输操作
// 支持取消的文件下载
class CancellableDownload(
    private val client: OkHttpClient,
    private val url: String,
    private val outputFile: File
) {
    private var call: Call? = null
    
    fun start(progressListener: ProgressListener? = null) {
        val request = Request.Builder().url(url).build()
        
        val clientWithProgress = if (progressListener != null) {
            client.newBuilder()
                .addNetworkInterceptor { chain ->
                    val originalResponse = chain.proceed(chain.request())
                    originalResponse.newBuilder()
                        .body(ProgressResponseBody(originalResponse.body!!, progressListener))
                        .build()
                }
                .build()
        } else {
            client
        }
        
        call = clientWithProgress.newCall(request)
        call!!.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                // 处理失败
            }
            
            override fun onResponse(call: Call, response: Response) {
                if (!response.isSuccessful) {
                    // 处理错误响应
                    return
                }
                
                response.body?.source()?.use { source ->
                    outputFile.sink().buffer().use { sink ->
                        sink.writeAll(source)
                    }
                }
                // 下载完成
            }
        })
    }
    
    fun cancel() {
        call?.cancel()
    }
}

通过OkHttp的强大功能,我们可以轻松实现各种复杂的文件传输需求,从简单的下载到复杂的多文件上传,再到带进度监控的流式处理,OkHttp都提供了简洁而强大的API支持。

超时控制与重试机制配置

在网络请求中,超时控制和重试机制是确保应用稳定性和可靠性的关键特性。OkHttp提供了细粒度的超时配置和智能的重试策略,让开发者能够根据具体业务需求进行精确调优。

超时配置详解

OkHttp支持多种类型的超时设置,每种超时控制不同的网络交互阶段:

1. 连接超时(Connect Timeout)

连接超时控制建立TCP连接的最大等待时间,默认值为10秒。这个超时适用于从开始连接到完成TCP握手的过程。

val client = OkHttpClient.Builder()
    .connectTimeout(5, TimeUnit.SECONDS)  // 设置5秒连接超时
    .build()

2. 读取超时(Read Timeout)

读取超时控制从服务器接收数据的最大等待时间,默认值为10秒。这个超时适用于从发送请求到接收完整响应的整个过程。

val client = OkHttpClient.Builder()
    .readTimeout(15, TimeUnit.SECONDS)  // 设置15秒读取超时
    .build()

3. 写入超时(Write Timeout)

写入超时控制向服务器发送数据的最大等待时间,默认值为10秒。这个超时适用于请求体的上传过程。

val client = OkHttpClient.Builder()
    .writeTimeout(8, TimeUnit.SECONDS)  // 设置8秒写入超时
    .build()

4. 完整调用超时(Call Timeout)

完整调用超时控制整个HTTP请求从开始到结束的最大时间,包括重试和重定向。默认情况下此超时未启用。

val client = OkHttpClient.Builder()
    .callTimeout(30, TimeUnit.SECONDS)  // 设置30秒完整调用超时
    .build()

超时配置的最佳实践

不同场景下的超时配置建议:

场景类型 连接超时 读取超时 写入超时 完整调用超时
内部API调用 3-5秒 10-15秒 5-8秒 30-60秒
外部API调用 5-8秒 15-30秒 8-15秒 60-120秒
文件上传 5秒 60-300秒 60-300秒 300-600秒
实时通信 3秒 30秒 3秒 60秒

重试机制配置

OkHttp的自动重试机制能够在连接失败时智能地进行重试,提高请求的成功率。

启用连接失败重试

val client = OkHttpClient.Builder()
    .retryOnConnectionFailure(true)  // 默认启用
    .build()

重试机制的工作原理

OkHttp的重试机制遵循以下规则:

flowchart TD
    A[发起请求] --> B{请求失败?}
    B -->|否| C[请求成功]
    B -->|是| D{失败类型可重试?}
    D -->|否| E[失败结束]
    D -->|是| F{重试次数<br>小于最大限制?}
    F -->|否| E
    F -->|是| G[等待短暂间隔]
    G --> H[选择新路由]
    H --> A

可重试的异常类型

OkHttp会自动重试以下类型的连接失败:

  • IO异常:连接超时、读取超时、写入超时
  • SSL握手异常:特定的SSL协议错误
  • 路由失败:特定IP地址的连接失败

不可重试的情况

以下情况不会自动重试:

  • 应用层错误(4xx、5xx状态码)
  • SSL证书验证失败
  • 协议错误
  • 非幂等的HTTP方法(POST、PATCH等)

自定义重试策略

对于更复杂的重试需求,可以通过拦截器实现自定义重试逻辑:

class RetryInterceptor(private val maxRetries: Int) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        var response: Response? = null
        var exception: IOException? = null
        
        for (attempt in 1..maxRetries) {
            try {
                response = chain.proceed(request)
                if (response.isSuccessful) {
                    return response
                }
            } catch (e: IOException) {
                exception = e
                if (attempt == maxRetries) {
                    break
                }
                // 指数退避策略
                Thread.sleep((100 * Math.pow(2.0, attempt.toDouble())).toLong())
            }
        }
        
        throw exception ?: IOException("Request failed after $maxRetries attempts")
    }
}

// 使用自定义重试拦截器
val client = OkHttpClient.Builder()
    .addInterceptor(RetryInterceptor(3))
    .build()

超时与重试的协同工作

超时和重试机制需要协同配置才能发挥最佳效果:

val client = OkHttpClient.Builder()
    .connectTimeout(5, TimeUnit.SECONDS)      // 快速失败连接问题
    .readTimeout(30, TimeUnit.SECONDS)        // 给重试留出足够时间
    .callTimeout(120, TimeUnit.SECONDS)       // 控制总体时间
    .retryOnConnectionFailure(true)           // 启用自动重试
    .build()

高级配置:按请求设置超时

除了客户端级别的全局配置,还可以为单个请求设置特定的超时:

val request = Request.Builder()
    .url("https://api.example.com/data")
    .build()

val call = client.newCall(request)
call.timeout().timeout(10, TimeUnit.SECONDS)  // 单个请求10秒超时

val response = call.execute()

监控和调试

为了有效监控超时和重试行为,可以添加事件监听器:

class TimeoutEventListener : EventListener() {
    override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {
        println("连接开始: ${System.currentTimeMillis()}")
    }
    
    override fun connectEnd(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol?) {
        println("连接结束: ${System.currentTimeMillis()}")
    }
    
    override fun callFailed(call: Call, ioe: IOException) {
        if (ioe is SocketTimeoutException) {
            println("请求超时: ${ioe.message}")
        }
    }
}

val client = OkHttpClient.Builder()
    .eventListener(TimeoutEventListener())
    .build()

性能优化建议

  1. 连接超时:设置相对较短的值(3-5秒),快速发现网络不可达情况
  2. 读取超时:根据API响应时间特性设置,通常15-30秒较为合适
  3. 写入超时:对于大文件上传,需要设置较长的超时时间
  4. 重试策略:结合指数退避算法,避免对服务器造成雪崩效应
  5. 监控告警:对频繁超时的请求建立监控和告警机制

通过合理配置超时和重试机制,可以显著提升应用的网络请求成功率和用户体验。OkHttp提供的细粒度控制让开发者能够根据具体业务场景进行精确调优。

OkHttp作为一个强大而灵活的HTTP客户端库,提供了丰富的功能来满足各种网络请求需求。从基本的同步异步请求处理,到复杂的文件传输和流式操作,再到精细的超时控制和重试机制,OkHttp都展现了其卓越的性能和易用性。通过合理配置和最佳实践,开发者可以构建出高效、稳定、可靠的网络应用,提升用户体验和系统性能。

登录后查看全文
热门项目推荐
相关项目推荐