diff --git a/src/ui/flutter/android/app/build.gradle b/src/ui/flutter/android/app/build.gradle index 35e6819e..c0a5a89e 100644 --- a/src/ui/flutter/android/app/build.gradle +++ b/src/ui/flutter/android/app/build.gradle @@ -60,3 +60,7 @@ android { flutter { source '../..' } + +dependencies { + implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.0.0' +} diff --git a/src/ui/flutter/android/app/src/main/java/com/calcitem/sanmill/MainActivity.java b/src/ui/flutter/android/app/src/main/java/com/calcitem/sanmill/MainActivity.java index f40fc3e1..69102b38 100644 --- a/src/ui/flutter/android/app/src/main/java/com/calcitem/sanmill/MainActivity.java +++ b/src/ui/flutter/android/app/src/main/java/com/calcitem/sanmill/MainActivity.java @@ -27,12 +27,25 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.GeneratedPluginRegistrant; +import org.json.JSONObject; +import android.app.Application; +import android.content.Context; +import android.util.Log; +import java.io.File; +import java.io.FileWriter; +import xcrash.TombstoneManager; +import xcrash.TombstoneParser; +import xcrash.XCrash; +import xcrash.ICrashCallback; + public class MainActivity extends FlutterActivity { private static final String ENGINE_CHANNEL = "com.calcitem.sanmill/engine"; private MillEngine engine; + private final String TAG_XCRASH = "xCrash"; + @Override public void onCreate(Bundle savedInstanceState) { @@ -87,4 +100,108 @@ public class MainActivity extends FlutterActivity { GeneratedPluginRegistrant.registerWith(flutterEngine); } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + + // callback for java crash, native crash and ANR + ICrashCallback callback = new ICrashCallback() { + @Override + public void onCrash(String logPath, String emergency) { + Log.d(TAG_XCRASH, "xCrash log path: " + (logPath != null ? logPath : "(null)") + ", emergency: " + (emergency != null ? emergency : "(null)")); + + if (emergency != null) { + debug(logPath, emergency); + + // Disk is exhausted, send crash report immediately. + sendThenDeleteCrashLog(logPath, emergency); + } else { + // Add some expanded sections. Send crash report at the next time APP startup. + + // OK + TombstoneManager.appendSection(logPath, "expanded_key_1", "expanded_content"); + TombstoneManager.appendSection(logPath, "expanded_key_2", "expanded_content_row_1\nexpanded_content_row_2"); + + // Invalid. (Do NOT include multiple consecutive newline characters ("\n\n") in the content string.) + // TombstoneManager.appendSection(logPath, "expanded_key_3", "expanded_content_row_1\n\nexpanded_content_row_2"); + + debug(logPath, null); + } + } + }; + + Log.d(TAG_XCRASH, "xCrash SDK init: start"); + + // Initialize xCrash. + XCrash.init(this, new XCrash.InitParameters() + .setAppVersion("0.0.0") // TODO + .setJavaRethrow(true) + .setJavaLogCountMax(10) + .setJavaDumpAllThreadsWhiteList(new String[]{"^main$", "^Binder:.*", ".*Finalizer.*"}) + .setJavaDumpAllThreadsCountMax(10) + .setJavaCallback(callback) + .setNativeRethrow(true) + .setNativeLogCountMax(10) + .setNativeDumpAllThreadsWhiteList(new String[]{"^xcrash\\.sample$", "^Signal Catcher$", "^Jit thread pool$", ".*mill.*", ".*engine.*"}) // TODO + .setNativeDumpAllThreadsCountMax(10) + .setNativeCallback(callback) + .setAnrRethrow(true) + .setAnrLogCountMax(10) + .setAnrCallback(callback) + .setPlaceholderCountMax(3) + .setPlaceholderSizeKb(512) + .setLogDir(getExternalFilesDir("xcrash").toString()) + .setLogFileMaintainDelayMs(1000)); + + Log.d(TAG_XCRASH, "xCrash SDK init: end"); + + // Send all pending crash log files. + new Thread(new Runnable() { + @Override + public void run() { + for(File file : TombstoneManager.getAllTombstones()) { + sendThenDeleteCrashLog(file.getAbsolutePath(), null); + } + } + }).start(); + } + + private void sendThenDeleteCrashLog(String logPath, String emergency) { + Log.d(TAG_XCRASH, "Skip sendThenDeleteCrashLog"); + // Parse + //Map map = TombstoneParser.parse(logPath, emergency); + //String crashReport = new JSONObject(map).toString(); + + // Send the crash report to server-side. + // ...... + + // If the server-side receives successfully, delete the log file. + // + // Note: When you use the placeholder file feature, + // please always use this method to delete tombstone files. + // + //TombstoneManager.deleteTombstone(logPath); + } + + private void debug(String logPath, String emergency) { + // Parse and save the crash info to a JSON file for debugging. + FileWriter writer = null; + try { + File debug = new File(XCrash.getLogDir() + "/debug.json"); + debug.createNewFile(); + writer = new FileWriter(debug, false); + writer.write(new JSONObject(TombstoneParser.parse(logPath, emergency)).toString()); + } catch (Exception e) { + Log.d(TAG_XCRASH, "debug failed", e); + } finally { + if (writer != null) { + try { + writer.close(); + Log.d(TAG_XCRASH, "xCrash log written"); + } catch (Exception ignored) { + } + } + } + } } \ No newline at end of file