Penyebab PPA dan cara menghindarinya

ANR (Application Not Responding) - Kesalahan yang terjadi ketika aplikasi tidak merespons. Akibatnya, kotak dialog terbuka yang meminta pengguna untuk menunggu atau menutup aplikasi.
alt gambar

Kondisi PPA


  • Acara masukan (tombol dan acara sentuh) tidak diproses selama 5 detik;
  • BroadcastReceiver (onRecieve ()) tidak diproses dalam waktu yang ditentukan (foreground - 10 s, background - 60 s);
  • ContentProvider tidak selesai dalam 10 detik.

Biasanya utas utama diblokir.

Jika Anda membaca artikel saya, Anda mungkin sudah terbiasa dengan fakta bahwa kami merangkak ke dalam kode sumber. Jadi mari kita lihat seperti apa PPA di bawah tenda .

Kelas AppErrors menangani tidak hanya ANR, tetapi juga kesalahan lain yang mungkin terjadi dalam aplikasi, termasuk crash. Metode handleShowAnrUi () baru saja membuka jendela menakutkan ini bagi banyak pengembang dan pengguna yang menampilkan ANR.

class AppErrors { ... void handleShowAnrUi(Message msg) { Dialog dialogToShow = null; synchronized (mService) { AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj; final ProcessRecord proc = data.proc; if (proc == null) { Slog.e(TAG, "handleShowAnrUi: proc is null"); return; } if (proc.anrDialog != null) { Slog.e(TAG, "App already has anr dialog: " + proc); MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.ALREADY_SHOWING); return; } Intent intent = new Intent("android.intent.action.ANR"); if (!mService.mProcessesReady) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); } mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; if (mService.canShowErrorDialogs() || showBackground) { dialogToShow = new AppNotRespondingDialog(mService, mContext, data); proc.anrDialog = dialogToShow; } else { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.CANT_SHOW); // Just kill the app if there is no dialog to be shown. mService.killAppAtUsersRequest(proc, null); } } // If we've created a crash dialog, show it without the lock held if (dialogToShow != null) { dialogToShow.show(); } } ... 

Namun, PPA tidak dimulai di sini. Seperti yang saya katakan di atas, salah satu penyebab pertama kesalahan ini adalah keterlambatan acara input, yaitu 5 detik. Dengan pencarian singkat, kita dapat menemukan di mana nilai ini ditetapkan.

 namespace android { // Default input dispatching timeout if there is no focused application or paused window // from which to determine an appropriate dispatching timeout. const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec 

Sekarang kita dapat melihat kode tempat bagian asli dipanggil. Ini terjadi di kelas InputManagerService .

  // Native callback. private long notifyANR(InputApplicationHandle inputApplicationHandle, InputWindowHandle inputWindowHandle, String reason) { return mWindowManagerCallbacks.notifyANR( inputApplicationHandle, inputWindowHandle, reason); } 

Dan di sini adalah mWindowManagerCallbacks di InputMonitor :

  if (appWindowToken != null && appWindowToken.appToken != null) { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. final AppWindowContainerController controller = appWindowToken.getController(); final boolean abort = controller != null && controller.keyDispatchingTimedOut(reason, (windowState != null) ? windowState.mSession.mPid : -1); if (!abort) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. return appWindowToken.mInputDispatchingTimeoutNanos; } } else if (windowState != null) { try { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. long timeout = ActivityManager.getService().inputDispatchingTimedOut( windowState.mSession.mPid, aboveSystem, reason); if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. return timeout * 1000000L; // nanoseconds } } catch (RemoteException ex) { } } return 0; // abort dispatching } 

Mari kita lihat lebih dekat inputDispatchingTimedOut (). Di sini kami hanya menampilkan pesan melalui ActivityManager tentang berakhirnya batas waktu dan membiarkan pengguna memutuskan apakah akan membatalkan tindakan atau terus menunggu. Dan di ActivityManagerService , AppErrors disebut dalam kasus crash atau ANR.

  private boolean makeAppCrashingLocked(ProcessRecord app, String shortMsg, String longMsg, String stackTrace) { app.crashing = true; app.crashingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); startAppProblemLocked(app); app.stopFreezingAllLocked(); return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace); } private void makeAppNotRespondingLocked(ProcessRecord app, String activity, String shortMsg, String longMsg) { app.notResponding = true; app.notRespondingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, activity, shortMsg, longMsg, null); startAppProblemLocked(app); app.stopFreezingAllLocked(); } 

Penyebab utama PPA


  • Kunci input / output
  • Kemacetan jaringan
  • Pemblokiran utas
  • Loop tanpa ujung
  • Logika bisnis terlalu lama

Menghindari ANR


  • Utas antarmuka pengguna utama menjalankan logika yang hanya terkait dengan antarmuka pengguna;
  • Perhitungan kompleks (misalnya, operasi basis data, operasi input-output, operasi jaringan, dll.) Dilakukan dalam aliran terpisah;
  • Gunakan Handler untuk berinteraksi antara utas antarmuka pengguna dan alur kerja;
  • Gunakan RxJava dll. untuk menangani operasi asinkron.

Cara menangkap PPA


  • Informasi tentang ANR dapat disimpan dalam file / data / anr / traces.txt, atau di jalur / data / anr / anr_ * yang berbeda *. Anda bisa mendapatkannya menggunakan perintah berikut:

     adb root adb shell ls /data/anr adb pull /data/anr/<filename> 
  • Gunakan proyek open source ANR-WatchDog untuk mendeteksi ANR
  • Lihat Menghindari ANR :)

PS Saya menerbitkan semua pilihan di saluran telegram @paradisecurity .

Source: https://habr.com/ru/post/id439086/


All Articles