StrictMode هي أداة مطور مدمجة في Android SDK تكتشف وتُبلغ عن عمليات الإدخال/الإخراج العرضية واستدعاءات الشبكة على الخيط الرئيسي للتطبيق في الوقت الفعلي. لا تُصلح الأخطاء بل تعمل ككاشف — تُلقي استثناءات أو تكتب في LogCat عند انتهاك السياسات المُكونة. وفقًا لـ Google, 2024، يمكن للتكوين الصحيح لـ StrictMode اكتشاف ما يصل إلى 80% من مشكلات الأداء قبل إصدار التطبيق.
النقاط الرئيسية
StrictMode هي واجهة برمجة تطبيقات (API) مضمنة في Android SDK منذ API Level 9 (Android 2.3 Gingerbread). مهمتها اكتشاف التنفيذ العرضي للعمليات الثقيلة على الخيط الرئيسي (UI) التي قد تمنع عرض الواجهة في وقت التشغيل. الخيط الرئيسي مسؤول عن معالجة إدخال المستخدم وحساب التخطيط والعرض — أي حظر أطول من 16 مللي ثانية يؤدي إلى فقدان إطار.
StrictMode تتبع مبدأ «فشل سريعاً» — اكتشاف المشكلة في أقرب وقت ممكن، ويفضل أن يكون ذلك في لحظة ظهورها الأول. بدلاً من انتظار شكاوى المستخدمين من البطء، يتلقى المطور إشارة (سجلات أو حوار أو تعطل) مباشرة في مرحلة التطوير. لا تتطلب الأداة مكتبات إضافية أو تكوين Gradle —فقط بضعة أسطر من الكود في Application.onCreate، وتعمل تلقائيًا على جميع الأجهزة.
StrictMode مصمم لجميع مطوري Android، بغض النظر عن الخبرة. للمبتدئين، يساعد في تكوين عادات جيدة (عدم إجراء طلبات الشبكة في خيط UI)؛ للمطورين ذوي الخبرة، يؤتمتة مراقبة الجودة في خط أنابيب CI/CD. المشاريع الكبيرة (Google, Uber, Spotify) تُدرج StrictMode في إصدارات التصحيح مع penaltyDeath، وتُعطله في إصدارات الإصدار عبر التحقق من BuildConfig.DEBUG.
StrictMode تعترض استدعاءات النظام التي قد تمنع الخيط وتقارنها بمجموعة السياسات النشطة. إذا تطابق الاستدعاء مع سياسة وتم تنفيذه على الخيط الرئيسي، تطبق StrictMode العقوبة المحددة. آلية الاعتراض مُطبقة عبر خطاف داخل العملية — لا تستخدم الانعكاس (reflection) وتعمل بأقل حمل إضافي.
عند تفعيل سياسة StrictMode، تحقن معالجها في نقاط دخول استدعاءات النظام (FileInputStream, FileOutputStream, Socket, URLConnection). عندما يستدعي التطبيق، على سبيل المثال، URLConnection.openStream على الخيط الرئيسي، تتحقق StrictMode من الخيط الحالي — إذا كان الخيط الرئيسي، تتفعل الأداة. في Android 6.0+، تم تعزيز الآلية: استدعاءات الشبكة على الخيط الرئيسي تولد NetworkOnMainThreadException حتى بدون StrictMode، لكن StrictMode تسمح أيضًا بالتحكم في إدخال/إخراج القرص.
يمكن أن يكون لكل سياسة نوع عقوبة خاص بها أو مجموعة: penaltyLog — يكتب في LogCat مع تتبع المكدس، penaltyDialog — يعرض حوارًا للمستخدم (التصحيح فقط)، penaltyDeath — يلقي استثناءً ويعطل التطبيق، penaltyDropBox — يحفظ البيانات في DropBoxManager للتحليل لاحقًا. لخطوط أنابيب CI/CD، يُوصى باستخدام penaltyDeath — يضمن عدم مرور أي دمج مع انتهاكات دون ملاحظة.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictMode تقسم السياسات إلى مستويين: ThreadPolicy (مستوى الخيط — ما لا يمكن فعله على الخيط الرئيسي) و VmPolicy (الآلة الافتراضية — تسرب الذاكرة والموارد). يتم تكوين كلا المستويين بشكل مستقل ويعملان بالتوازي.
على مستوى الخيط، تتحكم StrictMode في أربعة أنواع من الانتهاكات: قراءة القرص (detectDiskReads)، كتابة القرص (detectDiskWrites)، عمليات الشبكة (detectNetwork)، والاستدعاءات البطيئة المخصصة (detectCustomSlowCalls). disk_read يتفعل عند أي قراءة لـ SharedPreferences أو SQLite أو ملفات على الخيط الرئيسي. network يتفعل عند طلبات HTTP و WebSocket واتصالات Socket. في Android 11+، تمت إضافة detectUnbufferedIO لكشف الإدخال/الإخراج غير المُخزن.
VmPolicy تتحكم في التسربات على مستوى الآلة الافتراضية ART: detectActivityLeaks (الأنشطة التي لم يتم تدميرها)، detectLeakedClosableObjects (Cursor و Stream و Socket غير مغلقة)، detectLeakedRegistrationObjects (BroadcastReceiver و ServiceConnection غير مسجلة). إذا اكتشفت VmPolicy أن Activity تم إنشاؤها ولكن لم يتم تدميرها بعد استدعاء onDestroy، فإنها تُخرج تتبع مكدس كامل — مما يوفر ساعات من تصحيح أخطاء تسرب الذاكرة.
| السياسة | المستوى | ما تكتشفه |
|---|---|---|
| detectDiskReads | Thread | قراءة SharedPrefs و SQLite وملفات في خيط UI |
| detectDiskWrites | Thread | الكتابة إلى SharedPrefs و SQLite وملفات في خيط UI |
| detectNetwork | Thread | أي عمليات شبكة في خيط UI |
| detectActivityLeaks | VM | الأنشطة التي تنجو من onDestroy |
| detectLeakedClosableObjects | VM | Cursor و Stream و Socket غير مغلقة |
من خلال detectCustomSlowCalls، يمكنك وضع علامة على طرقك الخاصة على أنها «مشبوهة» وتلقي تحذير عند تجاوز حد معين. على سبيل المثال، إذا كانت طريقتك loadUserProfile() تستغرق عادةً 5 مللي ثانية ولكنها تستغرق أحيانًا 200 مللي ثانية — لفها في StrictMode.noteSlowCall(«loadUserProfile»). إذا تجاوزت المدة الحد (افتراضيًا 2000 مللي ثانية)، ستقوم StrictMode بتوليد عقوبة. يتم تكوين الحد عبر setSlowCallDurationThreshold.
التكوين الأساسي لـ StrictMode يستغرق 10 أسطر من الكود ويتم في طريقة onCreate لفئة Application مخصصة. القاعدة الرئيسية: StrictMode تُفعل فقط في إصدارات التصحيح — في إصدارات الإصدار تُبطئ التطبيق ويمكن أن تُنشئ نتائج إيجابية خاطئة.
أنشئ فئة تمدد Application، وسجلها في AndroidManifest.xml عبر السمة android:name، وأضف تكوين StrictMode. ThreadPolicy.Builder يتضمن جميع الكاشفات وجميع أنواع العقوبات (باستثناء dialog — يعمل فقط عند توصيل مصحح أخطاء). VmPolicy.Builder يُضيف كاشفات لتسرب Activity وكائنات Closable. للمشاريع الكبيرة (100+ شاشة)، يُوصى بتكوين VmPolicy مع penaltyDeath على كاشف تسرب Activity — إنه صارم لكنه فعال.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
للتحكم التلقائي في CI/CD، استخدم penaltyDeath — إذا انتهك أي اختبار السياسة، سيتعطل التطبيق مع استثناء. ادمجه مع Android Test Orchestrator لضمان تشغيل كل اختبار في عملية نظيفة. لاختبارات UI (Espresso, Compose Test)، اكتب TestRule مخصصًا يعترض انتهاكات StrictMode ويحولها إلى إخفاقات تأكيد. مثال: في @Before، فعّل StrictMode، وفي @After، تحقق من عدم وجود انتهاكات.
افتراضيًا، الحد لـ customSlowCall هو 2000 مللي ثانية، لـ disk_read و disk_write — بدون حد (أي عملية تفعل). من خلال setSlowCallDurationThreshold و setSlowIoDurationThreshold، يمكنك تعيين القيم الخاصة بك بالمللي ثانية. إذا كان تطبيقك يقرأ SharedPreferences بشكل شرعي على الخيط الرئيسي (إعدادات صغيرة)، قم بزيادة الحد إلى 10–20 مللي ثانية — سيؤدي ذلك إلى تصفية القراءات السريعة مع الاحتفاظ بالبطيئة.
StrictMode أداة قوية لكنها صعبة المراس. التكوين غير الصحيح يؤدي إلى ملايين النتائج الإيجابية الخاطئة، مما يتسبب في توقف المطورين عن الاهتمام بها. فيما يلي ممارسات مثبتة تم جمعها من تجربة فرق Android الكبيرة.
هذه قاعدة صارمة: StrictMode يجب أبدًا أن تكون نشطة في إصدارات الإصدار. استخدم علامة BuildConfig.DEBUG أو buildConfigField مخصص. في إصدارات الإصدار، تقوم العديد من المكتبات الخارجية بشكل شرعي بعمليات على الخيط الرئيسي (تهيئة SDK، كتابة ذاكرة التخزين المؤقت)، وستنشئ StrictMode نتائج إيجابية خاطئة. علاوة على ذلك، penaltyDialog في إصدار الإصدار سيعرض حوارًا للمستخدم النهائي — وهو أمر غير مقبول.
للمشاريع الصغيرة (1–10 شاشات)، قم بتكوين penaltyLog — السجلات كافية للتحليل اليدوي. للمشاريع المتوسطة (10–50 شاشة)، أضف penaltyDeath على network و customSlowCalls. للمشاريع الكبيرة (50+ شاشة)، فعّل المجموعة الكاملة من السياسات مع penaltyDeath في CI/CD، و penaltyLog للتطوير المحلي. هذا التدرج يمنع إرهاق المطورين بالأعطال الخاطئة مع التحكم الصارم في الجودة في خط الأنابيب.
بعض المكتبات (Firebase, Crashlytics, Adjust) تقوم بشكل شرعي بعمليات في الخلفية قد تكتشفها StrictMode بشكل غير صحيح. الحلول: أضف المكتبة إلى قائمة بيضاء عبر penaltyListener، أو حدّث المكتبة إلى إصدار مع تبديل صريح إلى خيط الخلفية، أو استخدم StrictMode.vmPolicy. في Android 11+، تم تقديم StrictMode.OnVmViolationListener للتصفية البرمجية للانتهاكات حسب تتبع المكدس.
// تصفية النتائج الإيجابية الخاطئة عبر penaltyListener
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictMode ليست أداة مراقبة الجودة الوحيدة في نظام Android البيئي. لفهم مكانتها، دعنا نقارنها مع Android Lint و Android Profiler و Perfetto عبر معايير رئيسية: وقت الفحص وعمق التحليل والأتمتة.
| المعيار | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| وقت الفحص | وقت التشغيل (أثناء تشغيل التطبيق) | وقت الترجمة (قبل الإطلاق) | وقت التشغيل (بعد الوفاة) |
| ما يفحصه | القرص، الشبكة، التسربات | XML، الكود، الموارد | CPU، الذاكرة، الشبكة، الطاقة |
| الأتمتة | CI/CD عبر penaltyDeath | مهمة Gradle + lint-baseline | يتطلب تحليلاً يدويًا |
| العمق | خيط UI فقط والتسربات | تحليل ثابت للكود | صورة كاملة للأداء |
| النتائج الإيجابية الخاطئة | متوسطة (تعتمد على المكتبات) | منخفضة (قواعد مكونة) | لا شيء (قياسات فعلية) |
أفضل استراتيجية هي الجمع بين الطرق الثلاث: Android Lint يكتشف الأخطاء الواضحة في وقت الترجمة (مثل IdleHandler المنسي)، StrictMode تكتشف المشكلات في وقت التشغيل، و Android Profiler / Perfetto يُستخدم للتحليل العميق عندما لا تعطي الأداتان الأوليان إجابات. في المشاريع الحقيقية (Google Maps, Instagram)، يتم تقديم StrictMode في الأسبوع الثاني من التطوير — مباشرة بعد إعداد البنية الأساسية.
دعنا نلقي نظرة على سيناريوهين حقيقيين حيث يساعد StrictMode في اكتشاف وإصلاح مشكلات الأداء: قراءة SharedPreferences على الخيط الرئيسي وتسرب Activity عبر استدعاء غير مسجل.
عند بدء تشغيل التطبيق، سيكتشف StrictMode مع سياسة detectDiskReads قراءة SharedPreferences على الخيط الرئيسي. الحل: تحميل التكوين بشكل غير متزامن عبر CoroutineScope أو تخزينه مؤقتًا في الذاكرة عند بدء التشغيل. SharedPreferences تقرأ بشكل متزامن ملف XML من القرص — حتى مع ملف صغير (1–2 كيلوبايت)، تستغرق العملية من 1 إلى 5 مللي ثانية، وحتى 20 مللي ثانية على الأجهزة الرخيصة، مما قد يؤدي إلى فقدان الإطارات.
// ❌ كود إشكالي — قراءة SharedPrefs في خيط UI
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → انتهاك!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ كود مصحح — قراءة عبر Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode مع VmPolicy.detectActivityLeaks ستكتشف Activity خرجت من المكدس (تم استدعاء finish) لكن كائن Activity يظل في الذاكرة بسبب مرجع ثابت أو استدعاء غير مسجل. السيناريو النموذجي: تسجيل EventBus أو LocationListener في onResume دون استدعاء unregister في onPause. VmPolicy ستُخرج تتبع مكدس يشير إلى السطر الذي تم فيه إنشاء المرجع.
// ❌ تسرب — استدعاء غير ملغى
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → تسرب!
}
override fun onPause() {
super.onPause()
// نسيت: locationManager.unregister(locationCallback)
}
الأسئلة الشائعة
StrictMode تُضيف بالفعل حملًا إضافيًا صغيرًا — يتم فحص كل استدعاء نظام مقابل السياسات. تأثير الأداء هو 1–3% في إصدارات التصحيح وغائب في إصدارات الإصدار (حيث يتم تعطيل StrictMode). عند تفعيل detectAll على الأجهزة القديمة (Android 6–8)، قد يصل الحمل الإضافي إلى 5%، لذلك يُوصى بتكوين السياسات الضرورية فقط.
نعم، StrictMode متوافقة تمامًا مع Jetpack Compose. سياسات القرص والشبكة تعمل على مستوى الإطار، بغض النظر عن إطار UI. علاوة على ذلك، في Compose تكون خطورة حظر UI أعلى — يعيد Compose رسم الإطارات بمعدل 120 إطارًا في الثانية على الأجهزة عالية معدل التحديث، لذلك تصبح 5 مللي ثانية إضافية في قراءة الملف أكثر وضوحًا.
بدءًا من Android 8.1 (API 27)، قد تستخدم SharedPreferences التخزين المؤقت في الذاكرة — إذا تمت قراءة الملف بالفعل، فلن تتفعل إعادة القراءة StrictMode. تأكد من استدعاء getSharedPreferences لأول مرة (قراءة باردة) وأن سياسة detectDiskReads نشطة. تحقق أيضًا من عدم تجاوز StrictMode في جزء بدون أب.
في اختبارات JUnit، استخدم StrictMode.allowThreadDiskReads() و StrictMode.allowThreadDiskWrites() في @Before، واستعد الإعدادات في @After عبر StrictMode.enableDefaults(). لاختبارات Instrumentation، استخدم TestRunner مخصصًا مع حفظ مؤقت للسياسة الأصلية. في اختبارات Espresso، من المناسب لف الكود الحساس لـ StrictMode في IdlingResource.
StrictMode تعمل فقط على منصة Android عبر Android SDK. في Kotlin Multiplatform (KMP)، لا يمكن لكود commonMain استخدام StrictMode، ولكن بالنسبة لـ androidMain يمكنك إضافتها كالمعتاد. بالنسبة لجزء iOS، استخدم نظيرًا — تأكيد DispatchQueue.main.async للخيط الرئيسي.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.