StrictMode — این یک ابزار توسعهدهنده داخلی در Android SDK است که در زمان واقعی عملیات تصادفی ورودی/خروجی و تماسهای شبکه در رشته اصلی برنامه را تشخیص داده و گزارش میدهد. این ابزار خطاها را برطرف نمیکند، بلکه به عنوان یک آشکارساز عمل میکند — هنگام نقض سیاستهای تعیینشده استثنا پرتاب میکند یا در LogCat مینویسد. طبق دادههای Google, 2024، پیکربندی صحیح StrictMode امکان تشخیص تا 80٪ مشکلات عملکرد را حتی قبل از انتشار برنامه فراهم میکند.
نکات اصلی
StrictMode — API ای است که از API Level 9 (Android 2.3 Gingerbread) در Android SDK گنجانده شده است. وظیفه آن شناسایی در زمان اجرای اجرای تصادفی عملیات سنگین در رشته اصلی (UI) است که میتوانند رندر رابط را مسدود کنند. رشته اصلی مسئول پردازش ورودی کاربر، محاسبه چیدمان و رندر است — هر مسدودیتی بیش از 16 میلیثانیه منجر به افت فریم میشود.
StrictMode از اصل fail fast
پیروی میکند — مشکل را در اولین فرصت، ترجیحاً در لحظه اولین ظهور آن، شناسایی کند. به جای انتظار برای شکایت کاربران در مورد کندی، توسعهدهنده سیگنال (لاگ، دیالوگ یا crash) را مستقیماً در مرحله توسعه دریافت میکند. این ابزار به کتابخانههای اضافی و پیکربندی Gradle نیاز ندارد — فقط چند خط کد در Application.onCreate کافی است و به طور خودکار در همه دستگاهها کار میکند.
StrictMode برای همه توسعهدهندگان Android، صرف نظر از تجربه، طراحی شده است. به مبتدیان کمک میکند عادتهای صحیح شکل دهند (درخواست شبکه در رشته UI انجام ندهند)، به باتجربهها — کنترل کیفیت را در خط لوله CI/CD خودکار کنند. پروژههای بزرگ (Google, Uber, Spotify) StrictMode را با penaltyDeath در بیلدهای debug فعال میکنند و در بیلدهای release از طریق بررسی BuildConfig.DEBUG غیرفعال میکنند.
StrictMode تماسهای سیستمی را که میتوانند رشته را مسدود کنند رهگیری میکند و آنها را با مجموعه سیاستهای فعال مقایسه میکند. اگر تماس با سیاست مطابقت داشته باشد و در رشته اصلی اجرا شود، StrictMode penalty (جریمه) تعیینشده را اعمال میکند. مکانیسم رهگیری از طریق هوک درونفرآیندی پیادهسازی شده است — از reflection استفاده نمیکند و با حداقل overhead کار میکند.
هنگام فعالسازی سیاست، StrictMode کنترلکننده خود را در نقطه ورود تماسهای سیستمی (FileInputStream, FileOutputStream, Socket, URLConnection) تزریق میکند. وقتی برنامه مثلاً URLConnection.openStream را در رشته اصلی فراخوانی میکند، StrictMode رشته فعلی را بررسی میکند — اگر main thread باشد، ابزار فعال میشود. در Android 6.0+ مکانیسم تقویت شده است: تماسهای شبکه در main thread حتی بدون StrictMode NetworkOnMainThreadException ایجاد میکنند، اما StrictMode به شما امکان کنترل disk I/O را نیز میدهد.
هر سیاست میتواند نوع penalty خود یا ترکیبی داشته باشد: penaltyLog — ثبت در LogCat با stack trace، penaltyDialog — نمایش دیالوگ به کاربر (فقط در debug)، penaltyDeath — پرتاب استثنا و crash برنامه، penaltyDropBox — ذخیره دادهها در DropBoxManager برای تحلیل بعدی. برای خط لوله CI/CD، penaltyDeath توصیه میشود — این تضمین میکند که هیچ merge با نقض بدون شناسایی عبور نمیکند.
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 (Activity که نابود نشدهاند)، detectLeakedClosableObjects (Cursor, Stream, Socket بسته نشده)، detectLeakedRegistrationObjects (BroadcastReceiver, ServiceConnection لغو نشده). اگر VmPolicy تشخیص دهد که Activity ایجاد شده اما پس از فراخوانی onDestroy نابود نشده است، full stack trace را خروجی میدهد — این کار ساعتها اشکالزدایی نشت حافظه را صرفهجویی میکند.
| سیاست | سطح | چه چیزی را تشخیص میدهد |
|---|---|---|
| detectDiskReads | Thread | خواندن SharedPrefs، SQLite، فایلها در رشته UI |
| detectDiskWrites | Thread | نوشتن در SharedPrefs، SQLite، فایلها در رشته UI |
| detectNetwork | Thread | هرگونه عملیات شبکه در رشته UI |
| detectActivityLeaks | VM | Activity که از onDestroy جان سالم به در بردهاند |
| detectLeakedClosableObjects | VM | Cursor, Stream, Socket بسته نشده |
از طریق detectCustomSlowCalls میتوانید متدهای خود را به عنوان مشکوک
علامتگذاری کنید و هنگام تجاوز از آستانه تعیینشده هشدار دریافت کنید. به عنوان مثال، اگر متد loadUserProfile() شما معمولاً 5 میلیثانیه اجرا میشود اما در برخی موارد 200 میلیثانیه طول میکشد — آن را در StrictMode.noteSlowCall(loadUserProfile
) بپیچید. اگر مدت زمان از آستانه (پیشفرض 2000 میلیثانیه) تجاوز کند، StrictMode penalty تولید میکند. آستانه از طریق setSlowCallDurationThreshold قابل تنظیم است.
پیکربندی پایه StrictMode 10 خط کد طول میکشد و در متد onCreate کلاس Application سفارشی انجام میشود. قانون اصلی: StrictMode فقط در بیلدهای debug فعال میشود — در بیلدهای release برنامه را کند میکند و میتواند هشدارهای کاذب ایجاد کند.
کلاسی ایجاد کنید که از Application ارثبری کند، آن را در AndroidManifest.xml از طریق ویژگی android:name ثبت کنید و پیکربندی StrictMode را اضافه کنید. ThreadPolicy.Builder همه آشکارسازها و همه انواع penalty را فعال میکند (به جز dialog — فقط زمانی کار میکند که دیباگر متصل باشد). VmPolicy.Builder آشکارسازهای نشت Activity و اشیاء Closable را اضافه میکند. برای پروژههای بزرگ (100+ صفحه) توصیه میشود VmPolicy را با penaltyDeath برای آشکارساز Activity Leaks پیکربندی کنید — سختگیرانه اما مؤثر.
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 استفاده کنید — اگر هر آزمایشی سیاست را نقض کند، برنامه با استثنا crash میکند. با Android Test Orchestrator ترکیب کنید تا هر آزمایش در یک فرآیند تمیز اجرا شود. برای تستهای UI (Espresso, Compose Test) یک TestRule سفارشی بنویسید که نقضهای StrictMode را رهگیری کرده و به assertion failure تبدیل کند. مثال: در @Before StrictMode را فعال کنید و در @After بررسی کنید که violation وجود نداشته باشد.
پیشفرض آستانه برای customSlowCall 2000 میلیثانیه است، برای disk_read و disk_write — بدون آستانه (هر عملیاتی فعال میکند). از طریق setSlowCallDurationThreshold و setSlowIoDurationThreshold میتوانید مقادیر خود را به میلیثانیه تنظیم کنید. اگر برنامه شما به طور موجه SharedPreferences را در رشته اصلی میخواند (تنظیمات کوچک)، آستانه را به 10–20 میلیثانیه افزایش دهید — این کار خواندنهای سریع را فیلتر میکند اما خواندنهای کند را باقی میگذارد.
StrictMode ابزاری قدرتمند اما حساس است. پیکربندی نادرست منجر به میلیونها هشدار کاذب میشود که باعث میشود توسعهدهندگان به آنها توجه نکنند. در زیر — روشهای اثباتشده جمعآوری شده از تجربه تیمهای بزرگ Android آورده شده است.
این یک قانون آهنین است: StrictMode هرگز نباید در بیلدهای release فعال باشد. از پرچم BuildConfig.DEBUG یا buildConfigField سفارشی استفاده کنید. در بیلدهای release، بسیاری از کتابخانههای شخص ثالث به طور موجه عملیات را در رشته اصلی انجام میدهند (راهاندازی SDK، نوشتن کش)، و StrictMode هشدارهای کاذب ایجاد میکند. علاوه بر این، penaltyDialog در بیلد release به کاربر نهایی دیالوگ نشان میدهد — که غیرقابل قبول است.
برای پروژههای کوچک (1–10 صفحه) penaltyLog را پیکربندی کنید — لاگها برای تحلیل دستی کافی است. برای پروژههای متوسط (10–50 صفحه) penaltyDeath را برای network و customSlowCalls اضافه کنید. برای پروژههای بزرگ (50+ صفحه) مجموعه کامل سیاستها را با penaltyDeath در CI/CD فعال کنید و برای توسعه محلی — penaltyLog. این درجهبندی به توسعهدهنده با crashهای کاذب فشار نمیآورد اما کیفیت را در خط لوله به شدت کنترل میکند.
برخی کتابخانهها (Firebase, Crashlytics, Adjust) به طور موجه عملیات را در پسزمینه انجام میدهند که StrictMode ممکن است به اشتباه تشخیص دهد. راهحلها: کتابخانه را از طریق penaltyListener به whitelist اضافه کنید، کتابخانه را به نسخهای با جابجایی صریح به background thread بهروزرسانی کنید، یا از StrictMode.vmPolicy استفاده کنید. در Android 11+ StrictMode.OnVmViolationListener برای فیلتر برنامهریزی شده نقضها بر اساس stacktrace اضافه شده است.
// فیلتر کردن false positives از طریق 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 |
|---|---|---|---|
| زمان بررسی | Runtime (هنگام اجرای برنامه) | Compile time (قبل از اجرا) | Runtime (post-mortem) |
| چه چیزی را بررسی میکند | دیسک، شبکه، نشتها | XML، کد، منابع | CPU، حافظه، شبکه، انرژی |
| خودکارسازی | CI/CD از طریق penaltyDeath | Gradle task + lint-baseline | نیاز به تحلیل دستی دارد |
| عمق | فقط رشته UI و نشتها | تحلیل استاتیک کد | تصویر کامل عملکرد |
| هشدارهای کاذب | متوسط (وابسته به کتابخانهها) | کم (قوانین پیکربندی شده) | ندارد (اندازهگیریهای واقعی) |
بهترین استراتژی — ترکیب هر سه رویکرد است: Android Lint خطاهای آشکار را در مرحله کامپایل میگیرد (مثلاً IdleHandler فراموش شده)، StrictMode مشکلات را در زمان اجرا تشخیص میدهد، و Android Profiler / Perfetto برای تحلیل عمیق هنگامی که دو ابزار اول جواب نمیدهند استفاده میشود. در پروژههای واقعی (Google Maps, Instagram) StrictMode در هفته دوم توسعه — بلافاصله پس از پیکربندی معماری پایه — پیادهسازی میشود.
دو سناریوی واقعی را بررسی میکنیم که در آنها StrictMode به تشخیص و رفع مشکلات عملکرد کمک میکند: خواندن SharedPreferences در رشته اصلی و نشت Activity از طریق callback ثبتنشده.
در هنگام راهاندازی برنامه، 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 → VIOLATION!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ کد اصلاحشده — خواندن از طریق Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode با VmPolicy.detectActivityLeaks Activity را تشخیص میدهد که از پشته خارج شده (finish فراخوانی شده) اما شیء Activity به دلیل مرجع استاتیک یا callback ثبتنشده همچنان در حافظه باقی مانده است. سناریوی معمول: ثبت EventBus یا LocationListener در onResume بدون فراخوانی unregister در onPause. VmPolicy stack trace را با اشاره به خطی که مرجع در آن ایجاد شده است خروجی میدهد.
// ❌ نشت — callback لغو نشده
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → LEAK!
}
override fun onPause() {
super.onPause()
// فراموش کردید: locationManager.unregister(locationCallback)
}
سوالات متداول
StrictMode واقعاً مقدار کمی overhead اضافه میکند — هر تماس سیستمی از نظر مطابقت با سیاستها بررسی میشود. تأثیر بر عملکرد 1–3٪ در بیلد debug است و در release وجود ندارد (جایی که StrictMode غیرفعال است). هنگام فعالسازی detectAll در دستگاههای قدیمی (Android 6–8) overhead میتواند به 5٪ برسد، بنابراین توصیه میشود فقط سیاستهای مورد نیاز را پیکربندی کنید.
بله، StrictMode کاملاً با Jetpack Compose سازگار است. سیاستهای disk و network در سطح فریمورک، مستقل از UI-فریمورک کار میکنند. علاوه بر این، در Compose بحرانیبودن مسدودیتهای UI بیشتر است — Compose فریمها را با فرکانس 120 FPS در دستگاههای با نرخ تازهسازی بالا بازپرداخت میکند، بنابراین 5 میلیثانیه اضافی برای خواندن فایل محسوستر میشود.
از Android 8.1 (API 27) به بعد، SharedPreferences ممکن است از کش در حافظه استفاده کند — اگر فایل قبلاً خوانده شده باشد، خواندن مجدد StrictMode را فعال نمیکند. بررسی کنید که getSharedPreferences را برای اولین بار فراخوانی میکنید (خواندن سرد) و سیاست detectDiskReads فعال است. همچنین بررسی کنید که StrictMode در fragment بدون parent بازنویسی نشده باشد.
در تستهای 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 assertion برای رشته اصلی استفاده کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید