Mất kết nối — nguyên nhân điển hình và phương pháp giải quyết

Tác giả: IT Sectr Đã đăng: 2026-07-29 Thời gian đọc: 10 phút

Mất kết nối — một trong những hiện tượng phổ biến và gây khó chịu nhất trong ứng dụng di động. Người dùng mất quyền truy cập dữ liệu, thao tác bị gián đoạn, ứng dụng bị đơ hoặc treo. Theo Google Android Developer Blog, 70% người dùng xóa ứng dụng nếu nó bị treo hoặc đơ hai lần. Hãy cùng tìm hiểu nguyên nhân mất kết nối và cách xây dựng hệ thống truyền thông chịu lỗi.

Những điểm chính

  • ANR (Application Not Responding) — chặn luồng UI quá 5 giây dẫn đến buộc kết thúc
  • Offline-first — kiến trúc nơi bộ nhớ cục bộ là nguồn dữ liệu gốc và mạng là cơ chế đồng bộ
  • Retry with backoff — tự động thử lại yêu cầu với độ trễ tăng dần khi có lỗi mạng
  • ConnectivityManager — API Android để theo dõi trạng thái mạng và điều chỉnh hành vi ứng dụng
  • Graceful degradation — ứng dụng nên hoạt động (ít nhất một phần) khi không có kết nối mạng

“Mất kết nối” trong ứng dụng di động nghĩa là gì?

Mất kết nối — thuật ngữ người dùng mô tả tình huống ứng dụng mất kết nối với máy chủ, ngừng phản hồi thao tác hoặc kết thúc với lỗi. Về mặt kỹ thuật, đây có thể là: lỗi mạng (hết thời gian chờ, lỗi DNS), ANR (đơ luồng UI), treo (ngoại lệ không được xử lý) hoặc điều kiện cạnh tranh (race condition).

Từ góc nhìn của người dùng, tất cả các kịch bản này đều giống nhau: ứng dụng ngừng hoạt động. Sự khác biệt đối với nhà phát triển nằm ở cách tiếp cận chẩn đoán và sửa chữa. Lỗi mạng được giải quyết bằng cơ chế thử lại, ANR bằng cách đưa thao tác ra khỏi luồng UI, treo bằng cách xử lý ngoại lệ.

Theo Crittercism (nay là Apteligent), trung bình một ứng dụng di động mất 1–2% người dùng sau mỗi lần treo. Đối với ứng dụng có 1 triệu người dùng, điều này có nghĩa là 10–20 nghìn lượt cài đặt bị mất cho một lỗi duy nhất. Điều này đặc biệt quan trọng đối với ứng dụng trong lĩnh vực tài chính và y tế.

Nguyên nhân chính gây mất kết nối

Mạng không ổn định — thiết bị di động liên tục chuyển đổi giữa Wi-Fi và mạng di động, đi vào khu vực không có phủ sóng (tàu điện ngầm, thang máy, tầng hầm). Mỗi lần chuyển đổi gây mất kết nối tạm thời mà ứng dụng phải xử lý đúng cách.

Hết thời gian chờ — nếu máy chủ không phản hồi trong thời gian chờ đã đặt (thường 10–30 giây), máy khách ném SocketTimeoutException. Thời gian chờ dài mà không có phản hồi bị người dùng coi là đơ. Nên đặt thời gian chờ không quá 15 giây.

kotlin
val client = OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(15, TimeUnit.SECONDS)
    .writeTimeout(15, TimeUnit.SECONDS)
    .retryOnConnectionFailure(true)
    .build()

Điều kiện cạnh tranh (race condition) — xảy ra khi nhiều luồng đọc và ghi cùng một dữ liệu đồng thời mà không đồng bộ hóa. Ví dụ, tải dữ liệu từ bộ nhớ đệm trong luồng UI song song với cập nhật bộ nhớ đệm từ mạng có thể dẫn đến hiển thị dữ liệu cũ hoặc không chính xác.

  • Ngoại lệ không được xử lý trong callback hoặc coroutine dẫn đến treo ứng dụng
  • Áp lực bộ nhớ — hệ thống giết ứng dụng khi không đủ bộ nhớ cho ứng dụng nền trước
  • Cạnh tranh vòng đời — thao tác bất đồng bộ hoàn thành sau khi Activity/Fragment đã bị hủy
  • Chặn UI — thực hiện thao tác mạng hoặc cơ sở dữ liệu trên luồng chính gây ANR sau 5 giây

Kiến trúc cho ứng dụng chịu lỗi

Offline-first — một mẫu kiến trúc nơi bộ nhớ cục bộ (Room, CoreData) là nguồn dữ liệu gốc duy nhất. Mạng được sử dụng để đồng bộ dữ liệu nền. Người dùng luôn thấy dữ liệu cập nhật từ bộ nhớ đệm cục bộ, ngay cả khi không có kết nối mạng.

Mẫu Repository — một điểm truy cập duy nhất cho dữ liệu quyết định lấy dữ liệu từ mạng hay từ bộ nhớ đệm. Repository trừu tượng hóa nguồn dữ liệu khỏi ViewModel và UI. Khi có lỗi mạng, repository tự động chuyển sang nguồn cục bộ.

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUsers(): Result<List<User>> {
        return try {
            val remote = api.fetchUsers()
            dao.insertAll(remote)
            Result.success(remote)
        } catch (e: IOException) {
            val cached = dao.getAll()
            if (cached.isNotEmpty()) {
                Result.success(cached) // return cache on network error
            } else {
                Result.failure(e)
            }
        }
    }
}

Circuit Breaker — một mẫu bảo vệ máy chủ khỏi làn sóng yêu cầu khi không khả dụng. Sau N lỗi liên tiếp, bộ ngắt mạch mở ra và tất cả yêu cầu ngay lập tức trả về lỗi mà không thử kết nối. Sau một thời gian chờ nhất định, bộ ngắt mạch chuyển sang trạng thái nửa mở cho một yêu cầu thử nghiệm.

Cách xử lý lỗi mạng?

Backoff theo cấp số nhân (Exponential backoff) — cơ chế thử lại tiêu chuẩn. Sau lần thất bại đầu tiên, chờ 1 giây; sau lần thứ hai, 2 giây; sau đó 4, 8, 16. Giới hạn số lần thử lại tối đa (thường 3–5) để không làm quá tải máy chủ và pin.

Phản hồi người dùng — khi có lỗi mạng, hiển thị thông báo rõ ràng: “Không có kết nối”, “Máy chủ tạm thời không khả dụng”, “Kiểm tra kết nối Internet”. Sử dụng Snackbar hoặc Inline State View. Không bao giờ hiển thị lỗi kỹ thuật (HTTP 500, SocketException) cho người dùng.

ConnectivityManager — API Android để giám sát mạng. Cho phép ứng dụng phản ứng với thay đổi: hiển thị trình giữ chỗ khi mất kết nối, tự động làm mới dữ liệu khi khôi phục. Trên iOS, sử dụng NWPathMonitor từ framework Network.

kotlin
class NetworkMonitor(private val context: Context) {
    private val manager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    fun isOnline(): Boolean {
        val network = manager.activeNetwork ?: return false
        val caps = manager.getNetworkCapabilities(network) ?: return false
        return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    }
}

Công cụ giám sát và ghi nhật ký

Crashlytics (Firebase) — công cụ báo cáo treo tiêu chuẩn cho ứng dụng di động. Nó thu thập stacktrace của tất cả ngoại lệ không được xử lý, phiên bản HĐH, mẫu thiết bị và thời gian treo. Nó cho phép nhóm lỗi và chỉ định người chịu trách nhiệm sửa lỗi.

Sentry — giải pháp thay thế Crashlytics với hỗ trợ giám sát hiệu suất. Nó cho phép theo dõi các giao dịch cụ thể (ví dụ: “ủy quyền người dùng”) và xem bước nào xảy ra lỗi. Theo dõi hiệu suất giúp phân biệt thời gian chờ mạng với lỗi trong logic ứng dụng.

Timber — thư viện ghi nhật ký cho Android với tính năng tự động thêm thẻ theo lớp. Trong bản dựng gỡ lỗi, ghi lại tất cả yêu cầu và phản hồi mạng. Trong bản dựng phát hành, chỉ ghi lại lỗi và cảnh báo qua Crashlytics.setCustomLog.

Công cụLoạiKhi nào sử dụng
CrashlyticsBáo cáo treoLuôn trong bản phát hành — tự động thu thập treo
SentryTreo + Hiệu suấtKhi cần phân tích kịch bản người dùng cụ thể
TimberGhi nhật kýGỡ lỗi: ghi đầy đủ; Phát hành: chỉ lỗi
HTTP ToolkitGỡ lỗi mạngChặn và phân tích lưu lượng HTTP cục bộ

Theo Firebase Summit 2023, các ứng dụng triển khai Crashlytics + Performance Monitoring giảm thời gian phát hiện và sửa lỗi nghiêm trọng trung bình từ 3 ngày xuống 4 giờ. Nên thiết lập cảnh báo cho mỗi lần treo với tần suất trên 0,1% người dùng hoạt động.

Câu hỏi thường gặp

Làm gì nếu ứng dụng treo mà không có lỗi?

Nếu treo không bị bắt trong Crashlytics, hãy kiểm tra treo gốc (SIGSEGV, SIGABRT) — chúng không được xử lý bởi trình xử lý ngoại lệ Java/Kotlin. Trên Android, đây có thể là rò rỉ bộ nhớ gốc từ JNI; trên iOS, EXC_BAD_ACCESS. Sử dụng Breakpad (Android) hoặc PLCrashReporter (iOS) để thu thập stacktrace treo gốc.

Làm thế nào để tái tạo lỗi chỉ xuất hiện trên mạng kém?

Sử dụng Network Link Conditioner (tích hợp trong iOS; cho Android, sử dụng Facebook Network Connection Class hoặc Developer Options > Network > Select network type). Đặt độ trễ 500–3000 ms và mất gói 5–30%. Bạn cũng có thể sử dụng Charles Proxy hoặc mitmproxy để mô phỏng độ trễ mạng và ngắt kết nối.

Làm thế nào để ngăn ANR trong quá trình yêu cầu mạng?

ANR xảy ra nếu luồng UI bị chặn quá 5 giây. Yêu cầu mạng nên được thực thi trong luồng nền: coroutine (viewModelScope.launch(Dispatchers.IO)), RxJava (subscribeOn(Schedulers.io)), hoặc WorkManager để đồng bộ hóa. Luôn đặt thời gian chờ trên máy khách HTTP — thiếu thời gian chờ có thể dẫn đến chặn vĩnh viễn.

Điều kiện cạnh tranh là gì và làm thế nào để tránh?

Điều kiện cạnh tranh — tình huống mà kết quả của thao tác phụ thuộc vào thứ tự thực thi luồng. Ví dụ, người dùng nhấn nhanh nút “Gửi” hai lần và yêu cầu được gửi hai lần. Giải pháp: sử dụng Mutex, bộ thực thi đơn luồng hoặc máy trạng thái (vô hiệu hóa nút sau lần nhấp đầu tiên). Trong Kotlin, sử dụng Mutex từ coroutine hoặc chú thích @Synchronized.

Làm thế nào để kiểm tra khả năng chịu lỗi của ứng dụng?

Áp dụng Chaos Engineering cho ứng dụng di động: ngắt mạng trong khi thao tác, mô phỏng độ trễ cao, chuyển đổi giữa Wi-Fi và mạng di động, giết tiến trình qua hệ thống. Công cụ: Facebook Network Connection Class, Charles Proxy, iOS Network Link Conditioner. Trong CI/CD, thêm kiểm thử UI với các điều kiện mạng khác nhau qua AndroidTest Orchestrator.

Tổng kết

  • Mất kết nối — thuật ngữ chung cho lỗi mạng, ANR, treo và điều kiện cạnh tranh; trải nghiệm người dùng giống nhau nhưng nguyên nhân khác nhau
  • Lỗi mạng — nguyên nhân phổ biến nhất; giải pháp bao gồm thời gian chờ (10–15 giây), backoff theo cấp số nhân và kiến trúc offline-first
  • ANR xảy ra khi luồng UI bị chặn quá 5 giây; luôn thực thi thao tác mạng và đĩa trong luồng nền
  • Offline-first với mẫu Repository: bộ nhớ cục bộ là nguồn dữ liệu gốc, mạng là cơ chế đồng bộ
  • Crashlytics + Performance Monitoring — bộ tối thiểu cho giám sát sản xuất với cảnh báo về treo thường xuyên
  • Điều kiện cạnh tranh yêu cầu đồng bộ hóa luồng: Mutex, máy trạng thái hoặc bộ thực thi đơn luồng
  • Kiểm thử với mô phỏng mạng kém và Chaos Engineering — đây là cách duy nhất để phát hiện vấn đề ẩn trong điều kiện phát triển lý tưởng

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm