LeakCanary — یک کتابخانه متنباز از Square برای تشخیص خودکار نشتهای حافظه در برنامههای Android است. این کتابخانه در فرآیند توسعه ادغام میشود و چرخه حیات Activity، Fragment، ViewModel و سایر مؤلفهها را در زمان واقعی ردیابی میکند و بلافاصله پس از وقوع نشتها هشدار میدهد. به گفته Square Open Source، این کتابخانه در هزاران پروژه استفاده میشود و استاندارد دوفاکتو برای تشخیص حافظه در Android محسوب میشود.
نکات اصلی
LeakCanary — کتابخانهای برای تشخیص خودکار نشتهای حافظه در برنامههای Android است که توسط شرکت Square توسعه یافته است. این کتابخانه در فرآیند ساخت برنامه ادغام میشود و به طور خودکار ردیابی میکند که آیا اشیایی که باید نابود شوند (Activity، Fragment، View) در حافظه باقی میمانند. هنگام تشخیص نشت، LeakCanary یک heap dump ایجاد کرده و زنجیره ارجاعات نگهدارنده شیء را تحلیل میکند.
این کتابخانه به استانداردی در جامعه Android تبدیل شده است: طبق دادههای GitHub، این پروژه بیش از 28 هزار ستاره جمعآوری کرده و در برنامههای Google، Uber، Airbnb و Facebook استفاده میشود. LeakCanary در دو نسخه اصلی موجود است: کلاسیک 1.x (با پیکربندی دستی) و مدرن 2.x (ادغام خودکار از طریق ContentProvider). نسخه 2.x نیاز به تغییر کلاس Application ندارد — یک وابستگی برای عملکرد کامل کافی است.
وظیفه اصلی LeakCanary تشخیص وضعیتی است که یک شیء پس از پایان چرخه حیات خود همچنان در حافظه وجود دارد. این امر برای نشتها از طریق فیلدهای ایستا، سینگلتونها، callbackهای ثبتنشده، کلاسهای ناشناس و بستههایی که اشیاء خارجی را ضبط میکنند، معمول است.
نشتهای حافظه در Android به دلیل حجم محدود RAM در دستگاههای موبایل بحرانیتر از دسکتاپ هستند. حتی نشت ۵–۱۰ مگابایت در هر جابهجایی بین صفحات میتواند پس از ۳۰–۴۰ دقیقه استفاده از برنامه منجر به OutOfMemoryError شود. LeakCanary چنین مشکلاتی را در مرحله توسعه شناسایی میکند، بدون اینکه منتظر خرابی در تولید بماند.
LeakCanary از ارجاعات ضعیف (WeakReference) در ترکیب با فراخوانی اجباری garbage collector استفاده میکند. هنگامی که Activity یا Fragment onDestroy را فراخوانی میکند، LeakCanary یک WeakReference برای آن شیء ایجاد کرده و پس از تأخیر کوتاه (پیشفرض ۵ ثانیه) GC را اجرا میکند. اگر پس از GC شیء همچنان از طریق WeakReference قابل دسترسی باشد، به این معنی است که توسط یک ارجاع قوی نگه داشته شده است — نشت ثبت میشود.
پس از تشخیص نشت، LeakCanary یک heap dump (تصویر کامل حافظه برنامه در قالب HPROF) ایجاد میکند. سپس تحلیلگر داخلی (Shark برای نسخه ۲.x) یک گراف دسترسی از GC Roots تا شیء نشتیافته ساخته و کوتاهترین مسیر — زنجیره ارجاعات نگهدارنده شیء در حافظه — را پیدا میکند.
// منطق سادهشده تشخیص LeakCanary
class ObjectWatcher {
private val watchedReferences = CopyOnWriteArrayList<KeyedWeakReference>()
fun watch(watchedObject: Any, description: String) {
val reference = KeyedWeakReference(watchedObject, description)
watchedReferences.add(reference)
BackgroundHandler.postDelayed({
checkForLeaks()
}, 5000)
}
private fun checkForLeaks() {
GcTrigger.runGc() // GC اجباری
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // شیء از GC جان سالم به در برد — این نشت است
}
}
}
}
نکته کلیدی — فراخوانی اجباری GcTrigger.runGc() است. بدون آن نمیتوان شیء واقعاً نشتیافته را از شیئی که GC هنوز فرصت جمعآوری آن را نداشته تشخیص داد. LeakCanary این کار را تا سه بار انجام میدهد: اگر پس از سه چرخه GC شیء همچنان در حافظه باشد — نشت تأیید میشود.
Shark — تحلیلگر heap dump تعبیهشده در LeakCanary 2.x است که به زبان Kotlin نوشته شده است. برخلاف تحلیلگر قبلی HAHA، Shark کل فایل HPROF را در حافظه بارگذاری نمیکند، بلکه گراف اشیاء آن را با حداقل تخصیصها پیمایش میکند. این امر مصرف RAM را در هنگام تحلیل از ۵۰ مگابایت به ۲–۵ مگابایت کاهش داده و زمان تحلیل را از ۳۰ ثانیه به ۱–۳ ثانیه میرساند.
نصب LeakCanary 2.x در یک پروژه مدرن Android یک خط در build.gradle طول میکشد. کتابخانه از ContentProvider برای راهاندازی خودکار استفاده میکند — نیازی به ویرایش کلاس Application یا افزودن کد در MainActivity نیست. اتصال فقط برای debug-build انجام میشود تا در APK انتشار کد اضافی وجود نداشته باشد.
// build.gradle (app/module)
dependencies {
// debugImplementation — کتابخانه فقط برای debug-build
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
پس از افزودن وابستگی و بازسازی پروژه، LeakCanary به طور خودکار در برنامه ظاهر میشود. در اولین اجرا، کتابخانه یک اعلان سیستمی درباره فعالسازی نشان میدهد. تمام نشتهای شناساییشده به صورت اعلان نمایش داده میشوند — کلیک روی اعلان صفحهای با گزارش جزئیات (LeakTrace) باز میکند.
برای سفارشیسازی میتوانید AppWatcherInstaller خود را ایجاد کرده و پارامترها را بازنویسی کنید: مهلت انتظار GC، لیست انواع اشیاء ردیابیشده، فعالسازی ذخیره heap dump روی دیسک. با این حال، برای ۹۰٪ پروژهها پیکربندی پیشفرض بهینه است.
از نسخه ۲.۱۲ به بعد، LeakCanary از ردیابی خودکار ViewModel، اسکوپهای کروتین و اشیاء State Compose پشتیبانی میکند. وابستگی اضافی مورد نیاز نیست — کتابخانه خود تشخیص میدهد که کدام مؤلفههای Jetpack در پروژه استفاده میشوند و آشکارسازهای مربوطه را فعال میکند.
گزارش LeakCanary (LeakTrace) — یک زنجیره چندخطی از ارجاعات از GC Root تا شیء نشتیافته است. هر خط کلاس و فیلدی را نشان میدهد که ارجاع قوی از آن عبور میکند. برنامهنویس باید زنجیره را از پایین به بالا بخواند: خط پایین — شیء نشتیافته، خط بالا — نقطه ورود (GC Root).
LeakTrace معمولی به این شکل است: GC Root → فیلد ایستا Application → سینگلتون → callback → Activity. اگر برنامهنویس چنین زنجیرهای ببیند، مشکل واضح است: سینگلتون یک callback را نگه میدارد که ارجاعی به Activity را ضبط کرده است. راهحل — جایگزینی ارجاع قوی با ضعیف در سینگلتون.
┬
├─ android.app.Application
│ Leaking: NO (Application — singleton)
│ ↓ Application.leakedActivities
├─ java.util.ArrayList
│ Leaking: NO (ArrayList — normal)
│ ↓ ArrayList[0]
├─ com.example.MainActivity
│ Leaking: YES (Activity destroyed but still in memory)
│ ↓ MainActivity.mCallback
├─ com.example.CallbackWrapper
│ Leaking: UNKNOWN
│ ↓ CallbackWrapper.mListener
│ ~~~~~~~~~~
├─ com.example.MyCallback (anonymous)
│ Leaking: UNKNOWN
│ ↓ MyCallback.this$0
├─ com.example.MainActivity
│ Leaking: YES (MainActivity is the leak)
╰
در این مثال LeakCanary نشان میدهد که MainActivity از طریق زنجیره نگه داشته شده است: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → دوباره MainActivity. فلش this$0 نشان میدهد که کلاس ناشناس MyCallback یک ارجاع خارجی به Activity را ضبط کرده است. راهحل — تبدیل callback به ارجاع ضعیف یا لغو آن در onDestroy.
LeakCanary همچنین وضعیت نشت را برای هر عنصر زنجیره نشان میدهد: NO (نشتی نیست — این عنصر ریشه است)، YES (شیء باید نابود شود)، UNKNOWN (تعیین وضعیت ممکن نبود). وضعیت UNKNOWN به معنای مشکل نیست — این یک شیء میانی است که LeakCanary نمیتواند به طور قطعی طبقهبندی کند.
انتقال از نسخه 1.x به 2.x اساسی بود: توسعهدهندگان کتابخانه را از نو نوشتند و تحلیلگر قدیمی HAHA را با موتور اختصاصی Shark که به زبان Kotlin نوشته شده جایگزین کردند. Shark بسیار سریعتر کار میکند، حافظه کمتری برای تحلیل نیاز دارد و علل ریشهای نشتها را دقیقتر تعیین میکند.
| پارامتر | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| زبان تحلیلگر | Java (HAHA — fork از Android SDK) | Kotlin (Shark — موتور اختصاصی) |
| نصب | پیکربندی دستی AppWatcher در Application | خودکار از طریق ContentProvider |
| سرعت | ۱۰–۳۰ ثانیه برای تحلیل heap dump | ۱–۵ ثانیه برای تحلیل heap dump |
| عملکرد | ۱۰–۵۰ مگابایت RAM در هنگام تحلیل | ۲–۱۰ مگابایت RAM در هنگام تحلیل |
مزیت کلیدی Shark — این است که کل heap dump را در حافظه بارگذاری نمیکند، بلکه گراف ارجاعات آن را با حداقل تخصیصها پیمایش میکند. این امر LeakCanary 2.x را برای استفاده در دستگاههای با RAM کم بدون خطر OutOfMemoryError در هنگام تحلیل مناسب میسازد.
در نسخه 2.x همچنین امکان صادرات heap dump به فایل برای تحلیل بعدی در Android Studio Memory Profiler اضافه شده است. برای این کار باید پارامتر dumpHeapWhenLeakFound را در پیکربندی AppWatcher فعال کنید.
LeakCanary به طور مؤثر چندین کلاس از نشتهای مشخصه Android را شناسایی میکند. شایعترین آنها نشت از طریق ارجاعات ایستا به Activity است — توسعهدهندگان ارجاع به زمینه Activity را در سینگلتون ذخیره میکنند و Activity پس از پایان چرخه حیات خود نمیتواند توسط GC جمعآوری شود.
دومین دسته از نظر فراوانی — نشتها از طریق شنوندگان ثبتنشده. اگر در onStart متد registerListener فراخوانی شده باشد اما در onStop/onDestroy متد unregisterListener فراخوانی نشده باشد — شیء شنونده حتی پس از نابودی فعالیت توسط سیستم نگه داشته میشود. LeakCanary به طور واضح نشان میدهد کدام شنونده و در کدام سرویس سیستم فعال باقی مانده است.
// نشت معمولی: Activity در callback سینگلتون ضبط شده است
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // ارجاع قوی به callback
}
fun unregister() {
callback = null // فراخوانی در onDestroy را فراموش نکنید!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda این را ضبط میکند
}
// اگر unregister در onDestroy فراخوانی نشود → نشت Activity
}
}
دسته سوم — نشتها از طریق Fragment در BackStack. اگر FragmentTransaction.addToBackStack() بدون حذف Fragment در هنگام بازگشت فراخوانی شود، نمونههای قدیمی Fragment در حافظه باقی میمانند. LeakCanary به شناسایی چنین نشتهای پنهانی در مراحل اولیه توسعه کمک میکند.
برای هر نشت شناساییشده، LeakCanary توضیحات و توصیههایی برای رفع ارائه میدهد. در نسخه ۲.۱۴ یکپارچهسازی با Android Lint اضافه شده است — کتابخانه میتواند هنگام تشخیص نشت در CI به طور خودکار وظایفی در issue tracker ایجاد کند.
سؤالات متداول
بله، حتماً. LeakCanary از طریق debugImplementation در build.gradle متصل میشود که به طور خودکار آن را از ساخت انتشار حذف میکند. اگر از طریق implementation متصل کنید، کتابخانه وارد APK انتشار شده و نشتها را به کاربران نهایی نشان میدهد — این غیرقابل قبول است.
تأثیر بر عملکرد حداقل است. LeakCanary فقط پس از onDestroy مؤلفه فعال میشود و در رندر UI یا پردازش لمس دخالت نمیکند. تنها هزینه — مکث کوتاه GC اجباری (حدود ۱۰۰ میلیثانیه) و نوشتن heap dump در هنگام نشت (صدم ثانیه).
LeakCanary به طور خودکار heap dump را در قالب HPROF در پوشه برنامه ذخیره میکند. فایل را میتوان از طریق Android Studio صادر کرد: Device File Explorer → data/data/com.example/files/leakcanary/. برای مشاهده، فایل را در Memory Profiler از طریق Capture → Open Heap Dump باز کنید.
بله، از نسخه ۲.۱۲ LeakCanary به طور کامل از Jetpack Compose پشتیبانی میکند. کتابخانه زمینههای Composition و اشیاء State را ردیابی میکند و به طور خودکار نشتها را در توابع Composable شناسایی میکند. پیکربندی جداگانه لازم نیست — بلافاصله کار میکند.
فعالسازیهای نادرست ممکن است اما نادر هستند. LeakCanary قبل از اعلام نشت از سه بار فراخوانی GC استفاده میکند که بیشتر موارد مثبت کاذب را حذف میکند. اگر فکر میکنید فعالسازی نادرست است — یک IgnoredReference برای کلاس خاص در پیکربندی ایجاد کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید