2

私のAndroidアプリは現在、クラッシュをキャプチャすることを目的としたカスタムを使用しており、アプリのユースケースのようにAndroidの強制終了ポップアップを回避するために手動で呼び出す前UncaughtExceptionHandlerに、将来数秒間アプリの再起動をスケジュールします。ユーザーは対話できませんデバイスで FC ダイアログの [OK] をタップし、アプリを再起動します。AlarmManagerProcess.killProcess(Process.myPid())

今、Firebase Crash レポートと統合したいのですが、間違った動作を恐れているので、ここに私の質問があります:

  1. UncaughtExceptionHandlerプロセスを強制終了する前にカスタムが Firebase Crash Report に例外を渡すようにコードを作成するにはどうすればよいですか? 呼び出すとThread.getDefaultUncaughtExceptionHandler()、Firebase Crash レポートUncaughtExceptionHandlerが表示されるので、それを呼び出すことができuncaughtException(...)ますか?
  2. Firebase Process.killProcess(Process.myPid())Crash レポート ライブラリがレポート作業を行うのを妨げる可能性がありますか? Firebase は、uncaughtException(...)戻る前に別のプロセスでクラッシュ レポートを開始したでしょうか? Firebaseは、FC ダイアログを表示して、UncaughtExceptionHandlerAndroid のデフォルトの へのコールバックを所有していますか?UncaughtExceptionHandler
  3. Process.killProcess(Process.myPid())デフォルトのプロセスに加えて、Firebase Crash Reporting プロセスを強制終了できますか?
  4. カスタムApplicationクラスが Firebase Crash Reporting プロセスでインスタンス化されているかどうかを検出するにはどうすればよいですか? 両方のプロセスを同じように扱うと、おそらく矛盾した状態になるでしょう。

私を助けようとする人に感謝します!

4

2 に答える 2

3

例外ハンドラでプロセスを強制終了すると、クラッシュを受け取ることができなくなります。即時送信または遅延送信のいずれかでクラッシュを永続化する機能が妨げられます。正常に動作するキャッチされていない例外ハンドラーを登録したライブラリに干渉する可能性があります。

実際、 Process.killProcess(Process.myPid()) は、Android 開発のアンチパターンです。Android アプリは、アプリをホストするプロセスの場合、ライフサイクル全体にまったく関与する必要はありません。Android がプロセスを管理するという事実は、ユーザーの利益のために設計された最適化です。

アプリでキャッチされない例外については、アプリを通常どおり終了させることを強くお勧めします。クラッシュの適切な影響を隠すことは、じゅうたんの下の汚れを一掃するようなものです。短期的な問題を解決しているかもしれませんが、実際に必要なのは、エラーを修正できるように通常のログ記録と処理を行うことです。

Firebase Crash Reporting が別のプロセスで例外を送信するという事実に依存しないでください。その他のプロセスは、完全な非ベータ リリースでは削除されます。

Application サブクラスの最適な状況は、動作しているプロセスにまったく依存しないことです。実際、Google の Android チームは、Application サブクラスの使用をまったく推奨していません。これは、マルチプロセス アプリで問題が発生するだけだからです。Application サブクラスを使用する必要がある場合は、複数のプロセス内で実行する必要があります。

于 2016-06-28T06:41:09.923 に答える
2

いくつかのテストの後、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 を開始しました。これにより、UncaughtExceptionHandlerFirebase Crash Reporting ライブラリが例外を取得した後、スケジュールされた再起動が発生する前に実行中のプロセスが強制終了されます(マジック ナンバーが必要です)。バックグラウンドスレッドがプロセスを強制終了したときに強制終了ダイアログを削除し、最初に機能しAlarmManager、アプリを起動して、クラッシュして再起動する機会があることを知らせました。

問題は、あいまいでまったく文書化されていない理由で、2回目が機能しなかったことです。AlarmManager を呼び出す再起動をスケジュールするコードが適切に実行された場合でも、アプリは再起動しません。

また、強制終了ポップアップは表示されません。Firebase Crash レポートが含まれているか (自動的に有効になっている)、この動作について何も変わらないことを確認した後、Android に関連付けられました (Android 4.4.2 を実行している Kyocera KC-S701 でテストしました)。

そのため、最終的に Firebase 自身がスロー可能なものUncaughtExceptionHandlerを報告するために呼び出したものを検索し、コードを自分で呼び出して、アプリがキャッチされていない上でどのように動作するかを自分で管理できることを確認しましたThrowable

Firebase がそのようなシナリオをどのように改善できるか 仮説的に名付けられたreportFatal(Throwable ex)メソッドを名前の難読化や文書化を行わないようにするか、Firebase がその中の をキャッチした後に何が起こるかを決定できるようにすることThrowableUncaughtExceptionHandler、愚かな Android のデフォルトに柔軟に委譲するのではなくUncaughtExceptionHandler、大いに役立ちます.

これにより、Android で実行される重要なアプリを開発する開発者は、ユーザーが実行できない場合でもアプリを実行し続けることができます (医療監視アプリ、監視アプリなどを考えてみてください)。

また、開発者がカスタム アクティビティを起動して、スクリーンショットなどを投稿する機能を使用して、それがどのように発生したかをユーザーに説明するように求めることもできます。

ところで、私のアプリは危機的な状況で人間の幸福を監視することを目的としているため、実行を停止することはできません。ユーザーの安全を確保するために、すべての例外を回復する必要があります。

于 2016-07-11T15:29:54.293 に答える