いくつかのテストの後、UncaughtException
. Throwable
私は 3 つの異なるアプローチを試みましたが、キャッチされていないものを「FirebaseCrash」に渡し、致命的なエラーと見なされるように少し調整した最初のコードのみです。
動作するコード:
final UncaughtExceptionHandler crashShield = new UncaughtExceptionHandler() {
private static final int RESTART_APP_REQUEST = 2;
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
reportFatalCrash(ex);
restartApp(MyApp.this, 5000L);
}
private void reportFatalCrash(Throwable exception) {
FirebaseApp firebaseApp = FirebaseApp.getInstance();
if (firebaseApp != null) {
try {
FirebaseCrash.getInstance(firebaseApp)
.zzg(exception); // Reports the exception as fatal.
} catch (com.google.firebase.crash.internal.zzb zzb) {
Timber.wtf(zzb, "Internal firebase crash reporting error");
} catch (Throwable t) {
Timber.wtf(t, "Unknown error during firebase crash reporting");
}
} else Timber.wtf("no FirebaseApp!!");
}
/**
* Schedules an app restart with {@link AlarmManager} and exit the process.
* @param restartDelay in milliseconds. Min 3s to let the user got in settings force
* close the app manually if needed.
*/
private void restartApp(Context context, @IntRange(from = 3000) long restartDelay) {
Intent restartReceiver = new Intent(context, StartReceiver_.class)
.setAction(StartReceiver.ACTION_RESTART_AFTER_CRASH);
PendingIntent restartApp = PendingIntent.getBroadcast(
context,
RESTART_APP_REQUEST,
restartReceiver,
PendingIntent.FLAG_ONE_SHOT
);
final long now = SystemClock.elapsedRealtime();
// Line below schedules an app restart 5s from now.
mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + restartDelay, restartApp);
Timber.i("just requested app restart, killing process");
System.exit(2);
}
};
Thread.setDefaultUncaughtExceptionHandler(crashShield);
理由と失敗した試みの説明
class から仮想的に名前が付けられたreportFatal(Throwable ex)
メソッドFirebaseCrash
が、まだ (そしてありがたいことに)public
である間にプロガードされた名前を持ち、次の署名を付けているのは奇妙です: zzg(Throwable ex)
.
このメソッドは公開されたままである必要がありますが、難読化されていません。
アプリが Firebase Crash Report ライブラリによって導入されたマルチプロセスで適切に動作するようにするために、アプリケーション クラスからコードを移動し (これは素晴らしいことでした)、代わりに遅延ロードされたシングルトンに配置する必要がありました。Doug Stevenson のアドバイスに従って、マルチプロセス対応になりました。
私のコードのどこにも、デフォルトの を呼び出し/委譲したことがわかりますUncaughtExceptionHandler
。これは Firebase Crash Reporting のものです。Android のものであるデフォルトのものを常に呼び出すため、私はそうしませんでした。これには次の問題があります。
Android のデフォルトに例外を渡す行の後に記述されたすべてのコードUncaughtExceptionHandler
は決して実行されません。これは、呼び出しがブロックされており、既に実行中のスレッドを除いて、プロセスの終了が後で発生する可能性があるためです。
アプリを終了させて再起動させる唯一の方法は、近い将来に再起動をスケジュールした後、System.exit(int whatever)
またはプログラムでプロセスを強制終了することです。Process.kill(Process.myPid())
AlarmManager
Thread
これにより、デフォルトを呼び出す前にnew を開始しました。これにより、UncaughtExceptionHandler
Firebase Crash Reporting ライブラリが例外を取得した後、スケジュールされた再起動が発生する前に実行中のプロセスが強制終了されます(マジック ナンバーが必要です)。バックグラウンドスレッドがプロセスを強制終了したときに強制終了ダイアログを削除し、最初に機能しAlarmManager
、アプリを起動して、クラッシュして再起動する機会があることを知らせました。
問題は、あいまいでまったく文書化されていない理由で、2回目が機能しなかったことです。AlarmManager
を呼び出す再起動をスケジュールするコードが適切に実行された場合でも、アプリは再起動しません。
また、強制終了ポップアップは表示されません。Firebase Crash レポートが含まれているか (自動的に有効になっている)、この動作について何も変わらないことを確認した後、Android に関連付けられました (Android 4.4.2 を実行している Kyocera KC-S701 でテストしました)。
そのため、最終的に Firebase 自身がスロー可能なものUncaughtExceptionHandler
を報告するために呼び出したものを検索し、コードを自分で呼び出して、アプリがキャッチされていない上でどのように動作するかを自分で管理できることを確認しましたThrowable
。
Firebase がそのようなシナリオをどのように改善できるか
仮説的に名付けられたreportFatal(Throwable ex)
メソッドを名前の難読化や文書化を行わないようにするか、Firebase がその中の をキャッチした後に何が起こるかを決定できるようにすることThrowable
でUncaughtExceptionHandler
、愚かな Android のデフォルトに柔軟に委譲するのではなくUncaughtExceptionHandler
、大いに役立ちます.
これにより、Android で実行される重要なアプリを開発する開発者は、ユーザーが実行できない場合でもアプリを実行し続けることができます (医療監視アプリ、監視アプリなどを考えてみてください)。
また、開発者がカスタム アクティビティを起動して、スクリーンショットなどを投稿する機能を使用して、それがどのように発生したかをユーザーに説明するように求めることもできます。
ところで、私のアプリは危機的な状況で人間の幸福を監視することを目的としているため、実行を停止することはできません。ユーザーの安全を確保するために、すべての例外を回復する必要があります。