این بخش نحوه اشکالزدایی یک برنامه در حال عدم پاسخگویی (ANR) را با استفاده از ProfilingManager با یک مثال ردیابی نشان میدهد.
تنظیم برنامه برای جمعآوری ANRها
با تنظیم یک تریگر ANR در برنامه خود شروع کنید:
public void addANRTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANR); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { // Handle uploading trace to your back-end }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
پس از ثبت و آپلود ردیابی ANR، آن را در رابط کاربری Perfetto باز کنید.
ردیابی را تجزیه و تحلیل کنید
از آنجا که ANR ردیابی را آغاز کرد، میدانید که ردیابی زمانی پایان مییابد که سیستم عدم پاسخگویی را در رشته اصلی برنامه شما تشخیص دهد. شکل 1 نحوه پیمایش به رشته اصلی برنامه شما را نشان میدهد که بر این اساس در رابط کاربری برچسبگذاری شده است.

همانطور که در شکل ۲ نشان داده شده است، انتهای مسیر با مهر زمانی ANR مطابقت دارد.

این ردیابی همچنین عملیاتهایی را که برنامه هنگام وقوع ANR در حال اجرا بوده است، نشان میدهد. به طور خاص، برنامه کدی را در برش ردیابی handleNetworkResponse اجرا کرده است. این برش درون برش MyApp:SubmitButton قرار داشت. این برش ۱.۴۸ ثانیه از زمان CPU را مصرف کرده است (شکل ۳).

اگر در لحظه ANR برای اشکالزدایی صرفاً به ردیابیهای پشته تکیه کنید، ممکن است به اشتباه ANR را کاملاً به کدی که در حال اجرا در برش ردیابی handleNetworkResponse است نسبت دهید که هنگام پایان ضبط پروفایل هنوز پایان نیافته است. با این حال، ۱.۴۸ ثانیه برای راهاندازی یک ANR به خودی خود کافی نیست، حتی اگر عملیاتی پرهزینه باشد. برای درک اینکه چه چیزی قبل از این روش، نخ اصلی را مسدود کرده است، باید به گذشته نگاه کنید.
برای یافتن نقطه شروعی برای جستجوی علت ANR، بررسی آخرین فریم تولید شده توسط نخ رابط کاربری را آغاز میکنیم که مربوط به برش Choreographer#doFrame 551275 است و قبل از شروع برش MyApp:SubmitButton که به ANR ختم میشود، منابع تأخیر زیادی وجود ندارد (شکل ۴).

برای درک انسداد، تصویر را کوچک کنید تا برش کامل MyApp:SubmitButton را بررسی کنید. همانطور که در شکل 4 نشان داده شده است، متوجه یک جزئیات مهم در حالتهای نخ خواهید شد: نخ 75٪ از زمان (6.7 ثانیه) را در حالت Sleeping و تنها 24٪ از زمان را در حالت Running گذرانده است.

این نشان میدهد که علت اصلی ANR انتظار بوده است، نه محاسبه. برای یافتن الگو، رویدادهای خواب را به صورت جداگانه بررسی کنید.




سه وقفه خواب اول (شکلهای ۶ تا ۸) تقریباً یکسان هستند، هر کدام تقریباً ۲ ثانیه. یک وقفه خواب چهارم (شکل ۹) ۰.۷ ثانیه است. مدت زمان دقیقاً ۲ ثانیه به ندرت در یک محیط محاسباتی تصادفی است. این موضوع قویاً نشاندهنده یک وقفه برنامهریزیشده است، نه یک تداخل تصادفی در منابع. آخرین خواب ممکن است ناشی از پایان انتظار نخ به دلیل موفقیت عملیاتی باشد که در انتظار آن بوده است.
این فرضیه این است که برنامه چندین بار به زمان انقضای ۲ ثانیهای تعریفشده توسط کاربر رسیده و در نهایت موفق شده و تأخیر کافی برای فعالسازی ANR ایجاد کرده است.

برای تأیید این موضوع، کد مرتبط با بخش ردیابی MyApp:SubmitButton را بررسی کنید:
private static final int NETWORK_TIMEOUT_MILLISECS = 2000; public void setupButtonCallback() { findViewById(R.id.submit).setOnClickListener(submitButtonView -> { Trace.beginSection("MyApp:SubmitButton"); onClickSubmit(); Trace.endSection(); }); } public void onClickSubmit() { prepareNetworkRequest(); boolean networkRequestSuccess = false; int maxAttempts = 10; while (!networkRequestSuccess && maxAttempts > 0) { networkRequestSuccess = performNetworkRequest(NETWORK_TIMEOUT_MILLISECS); maxAttempts--; } if (networkRequestSuccess) { handleNetworkResponse(); } } boolean performNetworkRequest(int timeoutMiliseconds) { // ... } // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
کد این فرضیه را تأیید میکند. متد onClickSubmit یک درخواست شبکه را در نخ رابط کاربری با NETWORK_TIMEOUT_MILLISECS به صورت کد ثابت با مدت زمان ۲۰۰۰ میلیثانیه اجرا میکند. نکته مهم این است که این متد درون یک حلقه while اجرا میشود که تا ۱۰ بار تلاش مجدد انجام میدهد.
در این ردیابی خاص، کاربر احتمالاً اتصال شبکه ضعیفی داشته است. سه تلاش اول ناموفق بود و باعث سه وقفه ۲ ثانیهای (در مجموع ۶ ثانیه) شد. تلاش چهارم پس از ۰.۷ ثانیه موفقیتآمیز بود و به کد اجازه داد تا به handleNetworkResponse ) ادامه دهد. با این حال، زمان انتظار انباشته شده، ANR را فعال کرده بود.
برای جلوگیری از این نوع ANR، عملیات مرتبط با شبکه که تأخیرهای متفاوتی دارند را به جای اجرای آنها در نخ اصلی، در یک نخ پسزمینه قرار دهید. این کار به رابط کاربری اجازه میدهد حتی با اتصال ضعیف نیز پاسخگو باقی بماند و این دسته از ANRها را به طور کامل از بین ببرد.