نکات و ترفندهای دیباگ در اندروید استودیو | به زبان ساده


همه ما به عنوان توسعهدهنده میدانیم که روزهایی هستند که بیش از ادیتور کد، وقت خود را در بخش دیباگر صرف میکنیم. بنابراین بهتر است که با نکات و ترفندهای دیباگ آشنا باشیم تا بتوانیم سرعت این کار را افزایش دهیم. بدین ترتیب در این مقاله با نکات و ترفندهای دیباگ در اندروید استودیو آشنا خواهیم شد تا در زمان مورد نیاز برای دیباگ کردن صرفهجویی کرده و این کار را به روش آسانتری اجرا کنیم.
در این مقاله برای آشنایی با فرایند دیباگ از یک اپلیکیشن بازی فرضی استفاده میکنیم. با این که اپلیکیشن مورد نظر شما ممکن است با این بازی متفاوت باشد، اما نکات و ترفندهایی که مطرح میشوند، روی هر نوع اپلیکیشن اندرویدی قابل استفاده هستند.
فیلتر کردن و خلاصهسازی Log
کار خود را با یک نکته در مورد روش کلاسیک دیباگ کردن آغاز میکنیم و آن استفاده از گزارههای printf است.
یک بازی را تصور کنید که نرخ رفرش فریم در هر ثانیه و امتیاز نهایی کاربر را در انتهای بازی به صورت لاگ ثبت میکند. بدین ترتیب در پنجره Logcat با چیزی مانند زیر مواجه میشویم:
این اطلاعات در خروجی بسیار حجیم است و موارد زیادی مانند تاریخ و ID-های ترد وجود دارند که مورد علاقه ما نیستند. برای خلوتتر کردن این خروجی میتوانیم آن را بر اساس نیازهای خود پیکربندی کنیم. به این منظور در نوار ابزار logcat روی آیکون Settings کلیک کرده و در دیالوگ Configure Logcat Header اطلاعاتی که نمیخواهید ببینید را از حالت انتخاب خارج کنید.
به این ترتیب خروجی لاگ بسیار تمیزتر و زیباتر مانند زیر به دست میآید:
با این حال، این لاگ همچنان حاوی بخشهای غیرضروری زیادی در مورد پیامهای مرتبط با امتیاز کاربر است. برای این که روی این پیامها متمرکز شویم، باید از جستجوی logcat استفاده کنیم. به این ترتیب بخشی از پیام دیباگ را در کادر Search وارد کنید تا پنجره logcat بر آن مبنا فیلتر شود.
اگر این جستجو را به صورت مکرر انجام میدهید، میتوانید آن را با افزودن یک فیلتر سفارشی از بخش Edit Filter Configuration ذخیره کنید:
سپس جزییات فیلترتان را اضافه کنید:
روش دیگر برای کاهش حجم لاگها این است که از قابلیت fold lines استفاده کنید. این قابلیت موجب میشود که خطوط طولانی که مشابه هم هستند با هم گروهبندی شده و به صورت فشرده نمایش یابند. به این منظور باید بخشی از متن یک آیتم لاگ را انتخاب و روی آن راست-کلیک کنید و سپس گزینه Fold Lines Like This را انتخاب نمایید.
زمانی که دیالوگ Console باز شد، روی OK کلیک کنید تا پیامهای مشابه، شامل متن منتخب به صورت گروهبندی درآیند.
اگر لازم است که این اطلاعات را در ادامه بررسی کنید، میتوانید روی خطوط کلیک کنید تا این گروهها باز شوند. همچنین دستگیرههای حاشیهای وجود دارند که به باز کردن خطوط گروهبندیشده کمک میکنند.
الصاق دیباگر
ما معمولاً فرایند دیباگ را با استفاده از کلیک روی دکمه Debug یا گزینههای منو آغاز میکنیم. با این حال، اگر یک اپلیکیشن را از قبل اجرا کرده باشید، میتوانید یک دیباگر را به این اپلیکیشن در حال اجرا الصاق کنید و نیازی هم به ریاستارت کردن آن وجود ندارد. به این منظور باید روی Attach Debugger to Android Process کلیک کنید.
در دیالوگ Choose Process پردازشی که میخواهید دیباگر را به آن الصاق کنید، انتخاب نموده و روی OK کلیک کنید. اکنون «نقاط توقف» (Breakpoints) به طور معمول مانند یک نشست دیباگ عمل میکنند.
جابجا کردن نقاط توقف
اگر دریافتید که یک نقطه توقف را در مکان نامناسبی قرار دادهاید، به جای پاک کردن آن و تنظیم مجدد نقطه توقف، میتوانید آن را روی خطی که باید باشد کشیده و رها کنید. این امر در مواردی مفید است که میخواهید تنظیمات نقطه توقف شامل مواردی که در بخش بعدی اشاره خواهیم کرد، حفظ شوند.
نقاط توقف شرطی
برخی اوقات لازم میشود یک باگ را ردگیری کنیم که در نوع خاصی از رویدادهای درون یک اپلیکیشن یا بازی فعال میشود. برای نمونه، تصور کنید مشغول برنامهنویسی یک بازی هستید و میخواهید بازیکن در حالتی که با شیئی تصادم کرده و آخرین مقدار جانش را نیز مصرف کرده است، متوقف شود. به این منظور یک نقطه توقف روی تصادمها تعیین میکنید، اما این نقطه توقف در هر بار تصادم بازیکن وارد عمل میشود. برای اجتناب از این مسئله باید یک نقطه توقف شرطی تعیین کنید.
برای تنظیم یک نقطه توقف شرطی، باید روی یک نقطه توقف راست-کلیک کرده و یک شرط به آن اضافه کنید. این شرط میتواند یک عبارت کد باشد که خروجی آن یک مقدار بولی است. زمانی که کد به این خط میرسد، اگر عبارت معادل True ارزیابی شود، نقطه توقف شرطی فعال میشود.
در این حالت، در منطق زمانی که بازیکن صرفاً با یک شیء تصادف میکند، تنظیم یک شرط به صورت زیر به ما کمک میکند که بازیکن را در آخرین لحظه مصرف کردن جانش و قبل از آن که سلامتیاش به 0 کاهش پیدا بکند، دریافت کنیم:
player.health == 1
نقاط توقف وابسته
معمولاً در یک اپلیکیشن با کدهایی سروکار نمییابیم که از چند مسیر مختلف اجرا شوند. اگر یک باگ وجود داشته باشد که تنها روی یک مسیر خاص فعال میشود، تعیین یک نقطه توقف در کد میتواند موجب پدید آمدن توقفهای غیرضروری زیادی شود. برای حل این مشکل، میتوانید از «نقاط توقف وابسته» (Dependent breakpoints) استفاده کنید که تنها پس از آن که نقطه توقف دیگری فعال شود، فعال میشوند. برای نمونه میتوانید یک نقطه توقف تنظیم کنید که تنها در مسیری که برای شما مهم است فعال میشود و سپس از آن به عنوان یک وابستگی استفاده کرده و نقطه توقف بعدی را در مسیر مورد علاقهتان تنظیم کنید.
برای تعیین یک نقاط توقف وابسته، باید روی نقطه توقف دوم راست-کلیک کرده و منوی More را باز کنید. در بخش Disable until breakpoint is hit آن نقطه توقفی که میخواهید این نقطه توقف به آن وابسته باشد را تعیین کنید.
به این ترتیب متوجه میشوید که آیکون نقطه توقف به شکل زیر درمیآید:
اکنون این نقطه توقف تنها در صورتی وارد عمل میشود که نقطه توقف قبلی فعال شده باشد. از این قابلیت میتوان در مواردی که نقطه توقف شرطی در جای دیگر وجود دارد نیز استفاده کرد و به این ترتیب دیگر نیازی به کپی کردن و چسباندن آن شرط در محل جدید وجود ندارد.
نخ تعلیق یافته
اگر یک اپلیکیشن «چندنخی» (multithreaded) را دیباگ میکنید، متوجه خواهید شد که به صورت پیشفرض، نقاط توقف موجب تعلیق یافتن همه نخها میشوند. با این حال، ممکن است بخواهید این رفتار را تغییر دهید. برای نمونه شاید بخواهید بررسی کنید که میتوانید یک نخ را مسدود کنید و بقیه اپلیکیشن همچنان در حال کار باشد و یا بخواهید در زمان بررسی یک وظیفه پسزمینه، UI به رندر کردن ادامه بدهد.
برای تعلیق تنها نخ جاری، باید گزینههای نقطه توقف را باز کرده و گزینه thread را در تنظیمات Supsend انتخاب کنید.
ارزیابی و لاگ کردن
برخی اوقات، به جای متوقف کردن یک نقطه توقف، نیاز داریم که برخی اطلاعات را در مورد حالت اپلیکیشن به دست آوریم. در این حالت میتوانید printlns را به کد اضافه کنید تا این وظیفه را انجام دهد. اما به جای بهرهگیری از این رویکرد که نیازمند کامپایل مجدد کد است، میتوانید از خود نقطه توقف برای ارزیابی و لاگ کردن استفاده کنید.
به این منظور در گزینههای breakpoint میتوانید گزینه Suspend را غیر فعال و گزینه Evaluate and log را فعال کنید.
اینک میتوانید هر عبارت کدری را اضافه کرده و مقادیر را ارزیابی کرده و در کنسول لاگ کنید. اگر صرفاً میخواهید صرفاً بررسی کنید که نقطه توقف فعال شده و اهمیتی به خصوصیات آن نمیدهید، در این صورت از پیام Breakpoint hit برای لاگ کردن این که نقطه توقف فعال شده بهره بگیرید. حتی یک روش سریعتر برای ایجاد این دسته از نقاط توقف وجود دارد و آن این است که کلید Shift کیبورد را بگیرید و روی نوار حاشیه کد (Gutter) در کد کلیک کنید.
غیر فعال کردن نقاط توقف
اگر میخواهید یک نقطه توقف را به صورت سریعی غیر فعال کنید، به جای راست-کلیک و غیر فعال کردن گزینه Enabled، میتوانید از کلیک وسط ماوس استفاده کرده و یا کلید Alt کیبورد را گرفته (در مک کلید Option) و چپ-کلیک کنید تا نقطه توقف فعال یا غیر فعال شود.
گروههای نقاط توقف
فرض کنید مشغول کار روی یک باگ هستید و برخی نقاط توقف را ایجاد میکنید، اما متوجه میشوید که پیشرفت کار کُند است. بنابراین شروع به کار روی یک باگ دیگر میکنید. با این حال به زودی متوجه میشوید که در حال توقف روی نقاط توقف باگ اول هستید. این برخورد با نقاط توقف بیربط کابوس توسعه دهنگان است و موجب میشود که از فرایند گردش دیباگ خارج شوید.
اما با استفاده از گروهبندی نقاط توقف میتوانید این مشکل را حل کنید. زمانی که نخستین نقطه توقف نامربوط فعال شد، روی آن راست-کلیک کرده و به بخش More بروید. اینک فهرستی از همه نقاط توقف میبینید و میتوانید همه نقاط توقف مرتبط با باگ اول را انتخاب کنید.
روی نقاط توقف منتخب راست-کلیک کنید و گزینه Move to group و سپس Create new را انتخاب کنید. نامی برای این گروه جدید تعیین کنید. این نام بهتر است با باگی که روی آن مشغول به کار هستید مرتبط باشد. اکنون میتوانید به سادگی همه نقاط توقف را با یک کلیک فعال یا غیر فعال کنید.
همچنین زمانی که کارتان پایان یافت، میتوانید از این گروه استفاده کرده و همه نقاط توقف مربوط به آن را به یک باره حذف کنید.
Drop frame
برخی اوقات، زمانی که مشغول بررسی یک کد تعلیق یافته هستید، ممکن است به صورت تصادفی به جای وارد شدن به یک متد از روی آن رد شوید (Step Over). اگر از اندروید 10 یا بالاتر استفاده میکنید، میتوانید با کلیک کردن روی Drop Frame در نوار ابزار دیباگر به سمت عقب بازگردید.
این قابلیت شما را از متد جاری خارج کرده و به جایی میبرد که هنوز متد جاری اجرا نشده بود. به این ترتیب فرصت مجددی برای ورود به این متد خواهید یافت.
این قابلیت یک «ماشین زمان» محسوب نمیشود. بنابراین اگر در میانه اجرای یک تابع بلند هستید و کار زیادی در آن تابع اجرا شده است (برای مثال حالت کلاس کنونی تغییر یافته است) این کار موجب نمیشود که کارها به عقب بازگردد.
Mark Object
در برخی موارد لازم است که چرخه عمر یک وهله کلاس خاص را پیگیری کنیم. برای مثال فرض کنید یک آیتم وجود دارد که کد @10140 را دارد:
شاید وسوسه شوید که یک تکه کاغذ بردارید و این عدد 10140 را بنویسید تا بتوانید دفعه بعد که این شیء ظاهر میشود، متوجه شوید. اما به طور جایگزین میتوانید روی آن آیتم راست-کلیک کرده و با کلیک روی Mark Object یک برچسب به آن بدهید.
اکنون هر جا که این شیء علامتدار در هر پنجره دیباگ ظاهر شود، به سادگی برچسب خورده و قابل شناسایی است. در تصویر زیر شیء مورد نظر ما را میبینید که دارای برچسب Myitem است:
آنچه که موجب میشود این قابلیت برای ردگیری یک شیء بهتر شود، این امکان است که میتوان در پنجره Watches حتی در صورت که در یک context کاملاً مجزا باشید که امکان دسترسی به آیتم نباشد نیز آن را مشاهده کرد. هر کجا که باشید، زمانی که یک نقطه توقف فعال شود در پنجره Watches برچسب مورد نظر خود را پس از عبارت _DebugLabel اضافه کنید.
اکنون میتوانید این آیتم کلاس را در هر جا در پنجره Watches ردگیری کرده و حالت آن را ببینید.
همچنین میتوانید این قابلیت را با نقاط توقف شرطی ترکیب کنید. برای نمونه میتوانید یک نقطه توقف تعیین کرده، روی آن راست-کلیک کنید و یک شرط برای بررسی آن با یک شیء برچسبدار تعیین کنید.
اکنون به جای رد کردن چند نقطه توقف تا یک وهله از آیتم خاص در دامنه، نقطه توقف درست در نقطه مورد نظر شما متوقف میشود.
ارزیابی عبارت
با این که پنجرههای Variables و Watches برای ردگیری مقادیر صریح مفید هستند، اما برخی اوقات لازم است که کد را به صورت آزادانهتری کاوش کنیم. به این منظور یک قابلیت برای «ارزیابی عبارت» (Evaluate expression) عرضه شده است. زمانی که در یک نقطه توقف هستید، میتوانید در نوار ابزار دیباگر به این قابلیت دسترسی داشته باشید.
در کادر متنی Expression میتوانید یک عبارت را وارد کرده و با زدن دکمه Evaluate آن را ارزیابی کنید. همچنین در صورتی که یک شیء را ارزیابی کنید، پس از ارزیابی میتوانید شیء را در بخش Result بررسی کنید.
پنجره ارزیابی عبارت را میتوان در یک حالت تکخطی نیز باز کرد. در این حالت میتوان به سادگی با کلیک کردن روی Expand آن را به صورت چندخطی باز کرد.
اکنون میتوانید عبارتهای پیچیده چندخطی را وارد کنید. این عبارتها میتوانند شامل متغیرها یا گروهها و موارد دیگر باشند.
اعمال تغییرات
زمانی که یک نقطه توقف شرطی دارید و میخواهید یک عبارت را ارزیابی کنید، حتی در صورتی که آن نقطه توقف فعال نشده باشد، دیباگر همچنان باید ارزیابی را انجام دهد. اگر نوعی کد را در یک حلقه واقعاً تنگ از قبیل پردازش انیمیشن یک بازی اجرا میکنید، این امر میتواند منجر به کد شدن بازی شود. با این که نقاط توقف شرطی مفید هستند، اما این امر منجر به وضعیت خاصی میشود که نمیتوان از آنها بهره گرفت. یک روش برای حل این مشکل این است که یک عبارت شرطی به کد اضافه کنید تا از یک عبارت no op استفاده شود و بتوانید یک نقطه توقف به آن الصاق کنید.
شما در زمان انجام این کار، ممکن است تصمیم بگیرید اپلیکیشن را ریاستارت و روی Debug کلیک کنید. اما به جای این کار کافی است در زمان اجرا روی اندروید 8 و بالاتر، از Apply Code Changes استفاده کنید.
اکنون کد وصله را دریافت کرده و عبارت جاسازیشده را اجرا میکند. با این حال، ممکن است در پنجره Frames ببینید که متد با عبارت Obsolete علامتگذاری شده است.
دلیل این امر آن است که کد وصله دریافت کرده است، اما دیباگر همچنان به کد قدیمی اشاره میکند. شما میتوانید از قابلیت drop frame برای ترک متد قدیمی و ورود به متد جدید استفاده کنید.
با این که ما در این مورد این کار را انجام ندادیم، اما گزینه دومی نیز وجود دارد. در این حالت میتوانید از گزینه Apply Changes and Restart Activity استفاده کنید. این گزینه برخلاف Apply Code Changes موجب ریاستارت شدن اکتیویتی میشود که در مواردی مفید است که منابع لیآوت یا کدی که تلاش میکنید دیباگ کنید، ویرایش شده باشند. برای نمونه در متد onCreate این موضوع به کار میآید.
آنالیز stacktrace
علیرغم همه این نکات و ترفندهایی که در این مقاله طرح کردیم، متأسفانه شما همچنان در کد خود باگهایی خواهید داشت و گزارش باگ را دریافت میکنید. زمانی که این گزارشهای باگ را دریافت میکنید، گزارشدهنده احتمالاً یک متن از پشته استثنا نیز ضمیمه کرده است. شما میتوانید این گزارش را با استفاده از اندروید استودیو به اطلاعات معنیدار تبدیل کنید. به این منظور باید به منوی stacktrace رفته و روی گزینه Analyze Stack Trace or Thread Dump را کلیک کنید.
این ابزار محلی برای چسباندن stack trace دارد، اما به طور خودکار با متن موجود در کلیپبورد پر میشود.
روی OK کلیک کنید تا یک نسخه کاملاً حاشیهنویسیشده از رد پشته به کنسول اضافه شود.
با یک نگاه میتوانید ببینید که به کدام بخش از کدبیس مربوط است و همچنین بخشهایی که احتمالاً نامربوط است با رنگ خاکستری مشخص میشود. همچنین میتوانید روی لینکها کلیک کنید تا مستقیماً به کدبیس بروید.
سخن پایانی
در این مقاله صرفاً برخی از نکات و ترفندهای دیباگ در اندروید استودیو را بررسی کردیم تا بتوانیم فرایند دیباگ خود را سرعت ببخشیم. برخی موارد مهم دیگر نیز وجود دارند که در این مقاله مجال پرداختن به آنها وجود نداشت، اما در ادامه اجمالاً به آنها اشاره میکنیم.
- در حالت دیباگ روی یک شماره خط در حاشیه کد کلیک کنید تا آن خط اجرا شود.
- فشردن همزمان کلید Ctrl و کشیدن، Ctrl+drag موجب میشود که یک کپی از نقطه توقف ایجاد شود.
- امکان تعیین نقاط توقف روی آکولاد پایانی یک تابع وجود دارد.
- امکان تعیین نقاط توقف روی فیلدها و مشخصهها نیز وجود دارد.
- امکان نقطه توقف روی یک متد اینترفیس برای توقف روی همه پیادهسازی آنها وجود دارد.
به این ترتیب به پایان مقاله میرسیم.