TL;DR
Kết nối TCP đã hết thời gian chờ trước khi server phản hồi. Có ba cách khắc phục phổ biến: tăng timeout OkHttp lên mức thực tế cho mạng di động, kiểm tra xem endpoint có thực sự truy cập được từ mạng của thiết bị không, và thêm logic retry cho các lỗi thoáng qua.
Lỗi gặp phải
java.net.SocketTimeoutException: failed to connect to api.example.com/192.168.1.1 (port 443) from /192.168.1.2 (port 52341) after 15000ms
Lỗi này xuất hiện trong logcat khi Android cố mở kết nối TCP tới server API nhưng không nhận được phản hồi trong khoảng thời gian timeout — 15 giây trong trường hợp này. Kết nối chưa bao giờ được thiết lập thành công. Đây là lỗi connect timeout, không phải read timeout. Sự phân biệt này rất quan trọng khi bạn bắt đầu debug.
Nguyên nhân
Có nhiều nguyên nhân có thể gây ra lỗi này — một số rõ ràng, một số không:
- Server không thể truy cập hoặc firewall chặn port 443 — gói SYN được gửi đi nhưng server không bao giờ phản hồi bằng SYN-ACK
- DNS phân giải ra IP sai hoặc đã cũ — địa chỉ IP trong thông báo lỗi không khớp với những gì bạn mong đợi
- Thiết bị đang dùng mạng bị hạn chế — WiFi công ty, VPN, hoặc hotspot chặn kết nối ra ngoài
- Timeout cấu hình quá thấp — OkHttp mặc định 10s cho connect/read/write, thường xuyên thất bại trên mạng 3G chậm hoặc tắc nghẽn
- Server đang quá tải — vẫn chấp nhận kết nối nhưng hàng đợi đầy và gói tin bị drop
Cách sửa 1: Điều chỉnh Timeout trong OkHttp / Retrofit
Hầu hết các setup Retrofit đều dùng OkHttp bên dưới. Các giá trị mặc định — 10s connect, 10s read, 10s write — hoạt động tốt trên kết nối nhanh nhưng thường xuyên thất bại trên mạng di động. Hãy tăng lên:
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // thời gian để thiết lập kết nối TCP
.readTimeout(30, TimeUnit.SECONDS) // thời gian chờ server phản hồi
.writeTimeout(30, TimeUnit.SECONDS) // thời gian gửi request body
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
30s là điểm khởi đầu hợp lý cho hầu hết API di động. Đừng vượt quá 60s — lúc đó bạn chỉ đang bắt người dùng nhìn vào màn hình chờ với một kết nối rõ ràng là không thể phục hồi.
Cách sửa 2: Thêm Logic Retry cho Lỗi Thoáng qua
OkHttp có cơ chế retry tích hợp sẵn, nhưng khá thận trọng. Với các sự cố mạng tạm thời hoặc trục trặc DNS, một custom retry interceptor có backoff sẽ đáng tin cậy hơn:
class RetryInterceptor(private val maxRetries: Int = 3) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
var lastException: IOException? = null
while (attempt < maxRetries) {
try {
return chain.proceed(chain.request())
} catch (e: SocketTimeoutException) {
lastException = e
attempt++
if (attempt < maxRetries) {
Thread.sleep(1000L * attempt) // backoff: 1s, 2s, 3s
}
}
}
throw lastException ?: IOException("Unknown error after $maxRetries retries")
}
}
// Gắn vào OkHttp
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(RetryInterceptor(maxRetries = 3))
.connectTimeout(30, TimeUnit.SECONDS)
.build()
Cách sửa 3: Thất Bại Nhanh Với Kiểm Tra Kết Nối
Chờ 30 giây để timeout là trải nghiệm người dùng rất tệ. Hãy kiểm tra tình trạng mạng trước khi thực hiện request — thất bại ngay lập tức với thông báo rõ ràng thay vì chờ đợi:
fun isNetworkAvailable(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = cm.activeNetwork ?: return false
val capabilities = cm.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
// Trong Repository của bạn
suspend fun fetchData(): Result<MyData> {
if (!isNetworkAvailable(context)) {
return Result.failure(IOException("Không có kết nối internet"))
}
return try {
Result.success(api.getData())
} catch (e: SocketTimeoutException) {
Result.failure(e)
}
}
Cách sửa 4: Debug Phân Giải DNS
Địa chỉ IP trong thông báo lỗi là manh mối đầu tiên. api.example.com/192.168.1.1 — đó là IP nội bộ. Nếu bạn đang gọi API công khai nhưng thấy địa chỉ 192.168.x.x hoặc 10.x.x.x, DNS đang phân giải sai host. Bộ nhớ cache cũ, DNS split-horizon, hoặc VPN cấu hình sai là những nguyên nhân thường gặp.
# Kiểm tra IP mà thiết bị phân giải
adb shell nslookup api.example.com
# Kiểm tra kết nối cơ bản
adb shell ping -c 3 api.example.com
# Kiểm tra xem port 443 có truy cập được không
adb shell nc -zv api.example.com 443
Nếu IP trông có vẻ sai, hãy chuyển từ WiFi sang dữ liệu di động và thử lại. Request thành công trên dữ liệu di động? Mạng WiFi đang lọc hoặc định tuyến sai lưu lượng của bạn — không phải lỗi code.
Cách sửa 5: Kiểm Tra Network Security Config
Android 9+ mặc định chặn HTTP cleartext. Nếu ứng dụng của bạn vô tình truy cập endpoint HTTP — URL base sai, hoặc redirect chuyển về HTTP — Android sẽ ngắt kết nối và hiển thị dưới dạng lỗi mạng. Kiểm tra cấu hình:
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
</domain-config>
</network-security-config>
Các Bước Xác Nhận
Sau khi đã áp dụng cách sửa, đây là cách xác nhận nó thực sự có tác dụng:
- Kiểm tra trên thiết bị thật, không phải emulator — mạng của emulator hoạt động khác biệt
- Lọc logcat theo tag
OkHttp— bạn sẽ thấy log request/response thay vì stack trace exception - Kiểm tra trên cả WiFi lẫn dữ liệu di động để phát hiện sự cố liên quan đến mạng cụ thể
- Thêm logging interceptor của OkHttp trong debug build để xem thời gian chi tiết:
// Chỉ dùng trong debug build
if (BuildConfig.DEBUG) {
val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS
}
builder.addInterceptor(logging)
}
Mẹo Hay
Nếu IP trong thông báo lỗi trông như địa chỉ nội bộ (10.x.x.x, 172.16–31.x.x, 192.168.x.x) nhưng bạn đang gọi API công khai, DNS đang dẫn bạn đến sai chỗ. IP Subnet Calculator trên ToolCraft giúp bạn nhanh chóng kiểm tra xem một IP là nội bộ hay có thể định tuyến công khai — chạy hoàn toàn trên trình duyệt, không upload gì lên server. Rất hữu ích khi debug ngoài thực địa mà không có công cụ quen thuộc.
Tài Liệu Tham Khảo
- Tài liệu OkHttp — hướng dẫn chính thức về timeout và interceptor
- Tài liệu Retrofit — cấu hình client
- Android: Đọc trạng thái mạng — ConnectivityManager API

