از دست رفتن اتصال — یکی از رایجترین و آزاردهندهترین پدیدهها در برنامههای موبایل است. کاربر دسترسی به دادهها را از دست میدهد، عملیات قطع میشود، برنامه هنگ میکند یا کرش میکند. طبق Google Android Developer Blog، ۷۰٪ کاربران برنامهای را که دو بار کرش یا هنگ کند حذف میکنند. دلایل از دست رفتن اتصال و روشهای ساخت ارتباطات مقاوم را بررسی میکنیم.
نکات کلیدی
قطع میشود — اصطلاحی کاربردی است که وضعیتی را توصیف میکند که برنامه ارتباط خود را با سرور از دست میدهد، به اقدامات پاسخ نمیدهد یا با خطا پایان مییابد. از نظر فنی میتواند: خطای شبکه (timeout، DNS failure)، ANR (مسدود شدن رشته UI)، crash (استثنای مدیریتنشده) یا race condition (شرط رقابتی) باشد.
برای کاربر همه این سناریوها یکسان به نظر میرسند: برنامه از کار میافتد. تفاوت برای توسعهدهنده در رویکرد تشخیص و رفع است. خطاهای شبکه با مکانیسمهای retry حل میشوند، ANR با خارج کردن عملیات از رشته UI، crash با مدیریت استثناها.
طبق Crittercism (اکنون Apteligent)، یک برنامه موبایل به طور متوسط ۱-۲٪ از کاربران خود را در هر کرش از دست میدهد. برای برنامهای با ۱ میلیون کاربر، این ۱۰-۲۰ هزار نصب از دست رفته به ازای هر باگ است. این امر به ویژه برای برنامههای بخش مالی و پزشکی بحرانی است.
شبکه ناپایدار — دستگاههای موبایل دائماً بین Wi-Fi و شبکه موبایل جابهجا میشوند، وارد مناطق بدون پوشش (مترو، آسانسور، زیرزمین) میشوند. هر جابهجایی باعث قطع موقت اتصال میشود که برنامه باید به درستی مدیریت کند.
تایماوتها — اگر سرور در مدت زمان تعیینشده (معمولاً ۱۰-۳۰ ثانیه) پاسخ ندهد، کلاینت SocketTimeoutException صادر میکند. تایماوتهای طولانی بدون بازخورد توسط کاربر به عنوان هنگ تلقی میشود. توصیه میشود تایماوت بیش از ۱۵ ثانیه تنظیم نشود.
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
شرط رقابتی (race condition) — زمانی رخ میدهد که چندین رشته به طور همزمان دادههای یکسانی را بدون همگامسازی میخوانند و مینویسند. به عنوان مثال، بارگذاری دادهها از کش در رشته UI به موازات بهروزرسانی کش از شبکه میتواند منجر به نمایش دادههای قدیمی یا نادرست شود.
Offline-first — الگوی معماری که در آن ذخیرهسازی محلی (Room، CoreData) تنها منبع حقیقت است. شبکه برای همگامسازی دادهها در پسزمینه استفاده میشود. کاربر حتی در غیاب شبکه همیشه دادههای بهروز را از کش محلی میبیند.
Repository pattern — نقطه ورود واحد برای دادهها که تصمیم میگیرد دادهها از شبکه یا کش گرفته شوند. مخزن منبع داده را از ViewModel و UI انتزاع میکند. در خطای شبکه، مخزن به طور خودکار به منبع محلی سوئیچ میکند.
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) // در خطای شبکه کش را برگردان
} else {
Result.failure(e)
}
}
}
}
Circuit Breaker — الگوی محافظت از سرور در برابر سیل درخواستها هنگام عدم دسترسی. پس از N خطای متوالی، کلید باز میشود و همه درخواستها بدون تلاش برای اتصال بلافاصله خطا برمیگردانند. پس از تایماوت مشخص، کلید به حالت نیمهباز برای درخواست آزمایشی میرود.
Exponential backoff — مکانیزم استاندارد retry. پس از اولین شکست ۱ ثانیه، پس از دومین ۲ ثانیه، سپس ۴، ۸، ۱۶ صبر کنید. حداکثر تعداد تلاشها را محدود کنید (معمولاً ۳-۵) تا سرور و باتری بیش از حد بارگذاری نشوند.
بازخورد کاربر — در خطای شبکه پیام قابل فهمی نشان دهید: «اتصال برقرار نیست»، «سرور موقتاً در دسترس نیست»، «اینترنت خود را بررسی کنید». از Snackbar یا Inline State View استفاده کنید. هرگز خطاهای فنی (HTTP 500، SocketException) را به کاربر نشان ندهید.
ConnectivityManager — Android API برای نظارت بر شبکه. به برنامه اجازه دهید به تغییرات واکنش نشان دهد: در قطع شبکه placeholder نشان دهد، در بازیابی به طور خودکار دادهها را بهروز کند. در iOS از NWPathMonitor از فریمورک Network استفاده کنید.
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)
}
}
Crashlytics (Firebase) — ابزار استاندارد گزارش کرش برای برنامههای موبایل. stacktrace همه استثناهای مدیریتنشده، نسخه سیستم عامل، مدل دستگاه و زمان کرش را جمعآوری میکند. امکان گروهبندی خطاها و تعیین مسئولان رفع را فراهم میکند.
Sentry — جایگزین Crashlytics با پشتیبانی از نظارت عملکرد. امکان ردیابی تراکنشهای خاص (مثلاً «احراز هویت کاربر») و دیدن اینکه خطا در کدام مرحله رخ داده است را فراهم میکند. Performance tracing به تشخیص تایماوتهای شبکه از باگهای منطق برنامه کمک میکند.
Timber — کتابخانه لاگگیری برای Android با افزودن خودکار برچسبها بر اساس کلاس. در بیلد debug همه درخواستها و پاسخهای شبکه را لاگ بگیرید. در بیلد release — فقط خطاها و هشدارها را از طریق Crashlytics.setCustomLog لاگ بگیرید.
| ابزار | نوع | زمان استفاده |
|---|---|---|
| Crashlytics | Crash reporting | همیشه در release — جمعآوری خودکار کرشها |
| Sentry | Crash + Performance | وقتی نیاز به پروفایل سناریوهای خاص کاربر دارید |
| Timber | Logging | Debug: لاگگیری کامل; Release: فقط خطاها |
| HTTP Toolkit | Network debug | رهگیری و تحلیل محلی ترافیک HTTP |
طبق Firebase Summit 2023، برنامههایی که Crashlytics + Performance Monitoring را پیادهسازی کردهاند، میانگین زمان تشخیص و رفع باگهای بحرانی را از ۳ روز به ۴ ساعت کاهش میدهند. توصیه میشود برای هر کرش با فراوانی بیش از ۰.۱٪ کاربران فعال، هشدار تنظیم کنید.
سوالات متداول
اگر crash در Crashlytics گرفته نمیشود، native crash (SIGSEGV, SIGABRT) را بررسی کنید — آنها توسط handler استثنای Java/Kotlin پردازش نمیشوند. در Android این میتواند نشت حافظه native از JNI باشد، در iOS — EXC_BAD_ACCESS. از Breakpad (Android) یا PLCrashReporter (iOS) برای جمعآوری stacktrace native crash استفاده کنید.
از Network Link Conditioner (داخلی در iOS، برای Android Facebook Network Connection Class یا تنظیمات Developer Options > Network > Select network type) استفاده کنید. تأخیر را ۵۰۰-۳۰۰۰ ms و افت بسته را ۵-۳۰٪ تنظیم کنید. همچنین میتوانید از Charles Proxy یا mitmproxy برای شبیهسازی تأخیرهای شبکه و قطعیها استفاده کنید.
ANR زمانی رخ میدهد که رشته UI بیش از ۵ ثانیه مسدود شود. درخواستهای شبکه باید در رشته پسزمینه اجرا شوند: coroutines (viewModelScope.launch(Dispatchers.IO))، RxJava (subscribeOn(Schedulers.io)) یا WorkManager برای همگامسازی. همیشه در کلاینت HTTP تایماوت تنظیم کنید — عدم وجود تایماوت میتواند منجر به مسدودیت دائمی شود.
Race condition — وضعیتی که نتیجه عملیات به ترتیب اجرای رشتهها بستگی دارد. مثلاً کاربر دو بار سریع دکمه «ارسال» را میزند و درخواست دو بار ارسال میشود. راهحل: از Mutex، single-threaded executors یا state machine استفاده کنید (دکمه را پس از اولین کلیک غیرفعال کنید). در Kotlin از Mutex از coroutines یا حاشیهنویسی @Synchronized استفاده کنید.
Chaos Engineering را برای برنامههای موبایل اعمال کنید: شبکه را در حین عملیات قطع کنید، تأخیر بالا شبیهسازی کنید، بین Wi-Fi و شبکه موبایل جابهجا شوید، فرآیند را توسط سیستم بکشید. ابزارها: Facebook Network Connection Class، Charles Proxy، iOS Network Link Conditioner. در CI/CD تستهای UI را با شرایط مختلف شبکه از طریق AndroidTest Orchestrator اضافه کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید