فقدان الاتصال — واحدة من أكثر الظواهر شيوعاً وإزعاجاً في التطبيقات المحمولة. يفقد المستخدم الوصول إلى البيانات، وتنقطع العملية، ويتجمد التطبيق أو يتعطل. وفقاً لـ Google Android Developer Blog، فإن 70% من المستخدمين يحذفون التطبيق إذا تعطل أو تجمد مرتين. دعنا نستعرض أسباب فقدان الاتصال وطرق بناء اتصالات متسامحة مع الأعطال.
النقاط الرئيسية
فقدان الاتصال — مصطلح يستخدمه المستخدم لوصف حالة يفقد فيها التطبيق الاتصال بالخادم، أو يتوقف عن الاستجابة للإجراءات، أو ينتهي بخطأ. من الناحية الفنية، يمكن أن يكون هذا: خطأ في الشبكة (مهلة، فشل DNS)، ANR (تجميد مؤشر ترابط واجهة المستخدم)، تعطل (استثناء غير معالج) أو حالة سباق (race condition).
من وجهة نظر المستخدم، تبدو كل هذه السيناريوهات متشابهة: يتوقف التطبيق عن العمل. الفرق للمطورين يكمن في نهج التشخيص والإصلاح. يتم حل أخطاء الشبكة بآليات إعادة المحاولة، وANR بإخراج العمليات من مؤشر ترابط واجهة المستخدم، والتعطل بمعالجة الاستثناءات.
وفقاً لـ Crittercism (الآن Apteligent)، في المتوسط يفقد التطبيق المحمول 1-2% من المستخدمين مع كل تعطل. لتطبيق لديه مليون مستخدم، هذا يعني 10-20 ألف عملية تثبيت مفقودة لكل خطأ واحد. هذا مهم بشكل خاص للتطبيقات في القطاعين المالي والطبي.
شبكة غير مستقرة — الأجهزة المحمولة تتنقل باستمرار بين شبكة Wi-Fi والشبكة الخلوية، وتدخل مناطق بدون تغطية (مترو الأنفاق، المصعد، الطابق السفلي). كل تبديل يسبب فقداناً مؤقتاً للاتصال يجب على التطبيق التعامل معه بشكل صحيح.
المهلات — إذا لم يستجب الخادم خلال المهلة المحددة (عادة 10-30 ثانية)، يطرح العميل استثناء SocketTimeoutException. المهلات الطويلة بدون ردود فعل ينظر إليها المستخدم على أنها تجميد. يوصى بتعيين مهلة لا تزيد عن 15 ثانية.
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
حالة السباق (race condition) — تحدث عندما تقرأ وتكتب عدة مؤشرات ترابط نفس البيانات في وقت واحد دون مزامنة. على سبيل المثال، تحميل البيانات من ذاكرة التخزين المؤقت في مؤشر ترابط واجهة المستخدم مع تحديث ذاكرة التخزين المؤقت من الشبكة يمكن أن يؤدي إلى عرض بيانات قديمة أو غير صحيحة.
Offline-first — نمط معماري حيث التخزين المحلي (Room, CoreData) هو المصدر الوحيد للحقيقة. تُستخدم الشبكة لمزامنة البيانات في الخلفية. يرى المستخدم دائماً بيانات محدثة من ذاكرة التخزين المؤقت المحلية، حتى بدون اتصال بالشبكة.
نمط المستودع (Repository pattern) — نقطة دخول واحدة للبيانات تقرر ما إذا كانت ستجلب البيانات من الشبكة أو من ذاكرة التخزين المؤقت. يقوم المستودع بتجريد مصدر البيانات من ViewModel وواجهة المستخدم. عند خطأ الشبكة، يتحول المستودع تلقائياً إلى المصدر المحلي.
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) — نمط يحمي الخادم من فيض الطلبات عندما يكون غير متاح. بعد N من الأخطاء المتتالية، يفتح قاطع الدائرة، وتعيد جميع الطلبات خطأ فوراً دون محاولة الاتصال. بعد مهلة محددة، ينتقل قاطع الدائرة إلى حالة نصف مفتوحة لطلب اختبار.
التراجع الأسي (Exponential backoff) — آلية إعادة محاولة قياسية. بعد الفشل الأول، انتظر ثانية واحدة؛ بعد الثاني، ثانيتين؛ ثم 4، 8، 16. حدد العدد الأقصى لإعادة المحاولات (عادة 3-5) لتجنب تحميل الخادم والبطارية.
ملاحظات المستخدم — عند خطأ الشبكة، أظهر رسالة واضحة: «لا يوجد اتصال»، «الخادم غير متاح مؤقتاً»، «تحقق من اتصالك بالإنترنت». استخدم Snackbar أو Inline State View. أبداً لا تظهر أخطاء فنية (HTTP 500, SocketException) للمستخدم.
ConnectivityManager — واجهة برمجة تطبيقات أندرويد لمراقبة الشبكة. اسمح للتطبيق بالتفاعل مع التغييرات: أظهر عنصراً نائباً عند فقدان الاتصال، وقم بتحديث البيانات تلقائياً عند الاستعادة. في 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) — أداة قياسية للإبلاغ عن الأعطال للتطبيقات المحمولة. تجمع تتبعات المكدس لجميع الاستثناءات غير المعالجة، وإصدار نظام التشغيل، وطراز الجهاز، ووقت التعطل. تسمح بتجميع الأخطاء وتعيين أشخاص مسؤولين للإصلاحات.
Sentry — بديل لـ Crashlytics مع دعم مراقبة الأداء. يسمح بتتبع المعاملات المحددة (مثل «تفويض المستخدم») ورؤية في أي خطوة حدث الخطأ. تتبع الأداء يساعد في تمييز مهلات الشبكة عن الأخطاء في منطق التطبيق.
Timber — مكتبة تسجيل لأندرويد مع إضافة تلقائية للعلامات حسب الفئة. في بنيات التصحيح، سجل جميع طلبات واستجابات الشبكة. في بنيات الإصدار، سجل الأخطاء والتحذيرات فقط عبر Crashlytics.setCustomLog.
| الأداة | النوع | متى تستخدم |
|---|---|---|
| Crashlytics | الإبلاغ عن الأعطال | دائماً في الإصدار — جمع تلقائي للأعطال |
| Sentry | أعطال + أداء | عند الحاجة لتحليل سيناريوهات مستخدم محددة |
| Timber | تسجيل | التصحيح: تسجيل كامل؛ الإصدار: الأخطاء فقط |
| HTTP Toolkit | تصحيح الشبكة | اعتراض وتحليل محلي لحركة مرور HTTP |
وفقاً لـ Firebase Summit 2023، التطبيقات التي طبقت Crashlytics + Performance Monitoring تقلل متوسط وقت اكتشاف وإصلاح الأخطاء الحرجة من 3 أيام إلى 4 ساعات. يوصى بإعداد تنبيهات لكل تعطل بتردد يتجاوز 0.1% من المستخدمين النشطين.
الأسئلة المتكررة
إذا لم يتم اكتشاف التعطل في Crashlytics، تحقق من الأعطال الأصلية (SIGSEGV, SIGABRT) — لا يتم معالجتها بواسطة معالج استثناءات Java/Kotlin. في أندرويد، قد يكون هذا تسرباً للذاكرة الأصلية من JNI؛ في iOS، EXC_BAD_ACCESS. استخدم Breakpad (أندرويد) أو PLCrashReporter (iOS) لجمع تتبعات مكدس الأعطال الأصلية.
استخدم Network Link Conditioner (مدمج في iOS؛ لأندرويد استخدم Facebook Network Connection Class أو Developer Options > Network > Select network type). اضبط تأخيراً من 500-3000 مللي ثانية وفقدان حزم من 5-30%. يمكنك أيضاً استخدام Charles Proxy أو mitmproxy لمحاكاة زمن الوصول وانقطاعات الشبكة.
يحدث ANR إذا كان مؤشر ترابط واجهة المستخدم محظوراً لأكثر من 5 ثوانٍ. يجب تنفيذ طلبات الشبكة في خلفية: كوروتينات (viewModelScope.launch(Dispatchers.IO))، RxJava (subscribeOn(Schedulers.io))، أو WorkManager للمزامنة. حدد دائماً مهلات على عميل HTTP — غياب المهلة يمكن أن يؤدي إلى حظر دائم.
حالة السباق — حالة تعتمد فيها نتيجة العملية على ترتيب تنفيذ المؤشرات. على سبيل المثال، يضغط المستخدم بسرعة على زر «إرسال» مرتين، ويتم إرسال الطلب مرتين. الحل: استخدم Mutex، منفذي خيط واحد أو آلة حالة (عطل الزر بعد النقرة الأولى). في Kotlin، استخدم Mutex من الكوروتينات أو التعليق @Synchronized.
طبق هندسة الفوضى (Chaos Engineering) للتطبيقات المحمولة: افصل الشبكة أثناء العمليات، وحاكِ زمن الوصول العالي، وبدّل بين Wi-Fi والشبكة الخلوية، واقتل العملية عبر النظام. الأدوات: Facebook Network Connection Class، Charles Proxy، iOS Network Link Conditioner. في CI/CD، أضف اختبارات واجهة المستخدم مع ظروف شبكة مختلفة عبر AndroidTest Orchestrator.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.