当前位置:   article > 正文

Android中Crash原理及监控处理_android crashlytics recordexception 多久控制台才接收

android crashlytics recordexception 多久控制台才接收

什么是Crash?

在应用启动及运行中,出现闪退(崩溃),屏幕提示当前程序停止运行的弹窗,类似于windows的应用程序崩溃。

为什么会出现崩溃?

概括来讲,就是程序运行中有未捕获的异常,未被 try-catch,导致进程被杀。

线程中抛出异常后的处理逻辑?

  1. 一旦线程出现异常,并且代码中为捕获的情况下,JVM 将调用 ThreaddispatchUncaughtException 方法把异常传递给线程的未捕获异常处理器。
  2. 默认情况下,线程是没有处理未捕获异常的能力的,线程组处理未捕获异常的逻辑是:首先将异常消息通知给父线程组,然后尝试利用一个默认的 defaultUncaughtExceptionHandler 来处理异常。
  3. JVM 提供给给我们设置每个线程的具体的未捕获的异常处理器,也提供了设置默认异常处理器的方法。
class Thread implements Runnable {
    //线程组
    private ThreadGroup group;

    //为当前线程单独设置的异常处理器
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    //所有线程通用的默认异常处理器,静态的
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    //当出现未捕获的异常 --》 线程分发异常
    public final void dispatchUncaughtException(Throwable e) {
        // END Android-added: uncaughtExceptionPreHandler for use by platform.
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        //默认情况下,线程的异常处理都为 null,除非我们手动设置,所有线程的异常都交由线程组统一处理
        return uncaughtExceptionHandler != null ?
                uncaughtExceptionHandler : group;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  1. 一旦线程出现未捕获的异常,JVM将调用ThreaddispatchUncaughtException方法将异常传递给线程的未捕获的异常处理器。

  2. 如果没有设置 UncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获的异常

  3. 线程 ThreadGroup 实现了 UncaughtExceptionHandler 接口来处理异常

public class ThreadGroup implements Thread.UncaughtExceptionHandler {

    public void uncaughtException(java.lang.Thread t, Throwable e) {
        //1.获取线程默认异常处理器,即在 RuntimeInitJava  -> commitInit 方法中设置的 KillApplicationHandler 异常处理器
        Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            //1.交由默认处理器来处理
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            //否则就打印在控制台上
            System.err.print("Exception in thread \""
                    + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. RuntimeInit#main 方法入口函数中,调用了 commonInit 方法设置了默认的异常处理器。
public class RuntimeInit {

    protected static final void commonInit() {
        //在这里通过静态调用 为线程设置了默认的异常处理器。
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. RuntimeInit#KillApplicationHandler的内部类中,该类也实现了 Thread.UncaughtExceptionHandler 接口。
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(java.lang.Thread t, Throwable e) {
        try {
            //在这里弹出崩溃弹窗
            ActivityManager.getService().handleApplicationCrash(
                    mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
        } catch (Throwable t2) {

        } finally {
            // Try everything to make sure this process goes away.
            //杀死进程,并退出 10,正常退出为 0.
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

其实在 fork 出 app 进程的时候,系统已经为 app 设置了默认的一个异常处理,并且最终崩溃后直接杀死 app,并退出。我们可以设置 Thread 的静态方法

Thread.setDefaultUncaughtExceptionHandler 自己设置了 Thread.UncaughtExceptionHandler 方法。

Java Crash监控

  • 如何收集 Java_Crash 日志
  • 如何重启应用
  • 如何还原混淆代码

1.如何收集 Java_Crash 日志

自定义java异常处理,记录当前设备的基础信息,及线程错误信息存储到本地文件中。在需要的时候上传到服务器。

CrashHandler

internal object CrashHandler {
    var CRASH_DIR = "crash_dir"
    fun init(crashDir: String) {
        Thread.setDefaultUncaughtExceptionHandler(CaughtExceptionHandler())
        this.CRASH_DIR = crashDir
    }

    private class CaughtExceptionHandler : Thread.UncaughtExceptionHandler {
        private val context = AppGlobals.get()!!
        private val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA)
        private val LAUNCH_TIME = formatter.format(Date())
        private val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        override fun uncaughtException(t: Thread, e: Throwable) {
            if (!handleException(e) && defaultExceptionHandler != null) {
                defaultExceptionHandler.uncaughtException(t, e)
            }
            restartApp()
        }

        private fun restartApp() {
            val intent: Intent? =
                context.packageManager?.getLaunchIntentForPackage(context.packageName)
            intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            context.startActivity(intent)

            Process.killProcess(Process.myPid())
            exitProcess(10)
        }


        private fun handleException(e: Throwable?): Boolean {
            if (e == null) return false
            val log = collectDeviceInfo(e)
            if (BuildConfig.DEBUG) {
                HiLog.e(log)
            }

            saveCrashInfo2File(log)
            return true
        }

        private fun saveCrashInfo2File(log: String) {
            val crashDir = File(CRASH_DIR)
            if (!crashDir.exists()) {
                crashDir.mkdirs()
            }
            val crashFile = File(crashDir, formatter.format(Date()) + "-crash.txt")
            crashFile.createNewFile()
            val fos = FileOutputStream(crashFile)

            try {
                fos.write(log.toByteArray())
                fos.flush()
            } catch (ex: Exception) {
                ex.printStackTrace()
            } finally {
                fos.close()
            }
        }


        /**
         * 设备类型、OS本版、线程名、前后台、使用时长、App版本、升级渠道

        CPU架构、内存信息、存储信息、permission权限
         */
        private fun collectDeviceInfo(e: Throwable): String {
            val sb = StringBuilder()
            sb.append("brand=${Build.BRAND}\n")// huawei,xiaomi
            sb.append("rom=${Build.MODEL}\n") //sm-G9550
            sb.append("os=${Build.VERSION.RELEASE}\n")//9.0
            sb.append("sdk=${Build.VERSION.SDK_INT}\n")//28
            sb.append("launch_time=${LAUNCH_TIME}\n")//启动APP的时间
            sb.append("crash_time=${formatter.format(Date())}\n")//crash发生的时间
            sb.append("forground=${ActivityManager.instance.front}\n")//应用处于前后台
            sb.append("thread=${Thread.currentThread().name}\n")//异常线程名
            sb.append("cpu_arch=${Build.CPU_ABI}\n")//armv7 armv8

            //app 信息
            val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
            sb.append("version_code=${packageInfo.versionCode}\n")
            sb.append("version_name=${packageInfo.versionName}\n")
            sb.append("package_name=${packageInfo.packageName}\n")
            sb.append("requested_permission=${Arrays.toString(packageInfo.requestedPermissions)}\n")//已申请到那些权限


            //统计一波 存储空间的信息,
            val memInfo = android.app.ActivityManager.MemoryInfo()
            val ams =
                context.getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
            ams.getMemoryInfo(memInfo)

            sb.append("availMem=${Formatter.formatFileSize(context, memInfo.availMem)}\n")//可用内存
            sb.append("totalMem=${Formatter.formatFileSize(context, memInfo.totalMem)}\n")//设备总内存

            val file = Environment.getExternalStorageDirectory()
            val statFs = StatFs(file.path)
            val availableSize = statFs.availableBlocks * statFs.blockSize
            sb.append(
                "availStorage=${Formatter.formatFileSize(
                    context,
                    availableSize.toLong()
                )}\n"
            )//存储空间


            val write: Writer = StringWriter()
            val printWriter = PrintWriter(write)
            e.printStackTrace(printWriter)
            var cause = e.cause
            while (cause != null) {
                cause.printStackTrace(printWriter)
                cause = cause.cause
            }

            printWriter.close()
            sb.append(write.toString())
            return sb.toString()
        }
    }

    fun crashFiles(): Array<File> {
        return File(
            AppGlobals.get()?.cacheDir,
            CRASH_DIR
        ).listFiles()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128

2. 重启应用

注意事项:如果进入应用就崩溃,就会进入重复重启的死循环。所以在做重启时候,需要做一些判断:例如1分钟内系统出现两次 crash 就不在重启了,否则这个应用被卸载无疑。

3.混淆日志代码的还原

**工具:**在SDK的的目录下:D:\software_for_code\Android\SDK\tools\proguard\bin
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5545iRNY-1638338068185)(../../pic/image-20210826195324102.png)]
使用方法:

  1. 双击打开 proguardgui.bat,找到app工程目下的编译后的 mapping file 文件中选择: mapping.txt,前提是代码开了混淆。将要还原的代码复制到窗口中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fn4bLGhl-1638338068187)(../../pic/image-20210826195729683.png)]

  1. 点击 ReTrace 即可。

Native crash异常处理

  • native_crash,就是 native 层发生了 crash。内部是通过一个 NativeCrashListener 线程去监控的。
  • SystemServer --> ActivityManagerService --> startObservingNativeCrashes();
class ActivityManagerService{
    public void startObservingNativeCrashes() {
        final NativeCrashListener ncl = new NativeCrashListener(this);
        ncl.start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

直接引入第三方库来处理 native 异常

3.封装工具类

object CrashMgr {
    private const val CRASH_DIR_JAVA = "java_crash"
    private const val CRASH_DIR_NATIVE = "native_crash"
    fun init() {
        val javaCrashDir = getJavaCrashDir()
        val nativeCrashDir = getNativeCrashDir()

        CrashHandler.init(javaCrashDir.absolutePath)
        NativeCrashHandler.init(nativeCrashDir.absolutePath)
    }

    private fun getJavaCrashDir(): File {
        val javaCrashFile = File(AppGlobals.get()!!.cacheDir, CRASH_DIR_JAVA)
        if (!javaCrashFile.exists()) {
            javaCrashFile.mkdirs()
        }
        return javaCrashFile
    }
    
    private fun getNativeCrashDir(): File {
        val nativeCrashFile = File(AppGlobals.get()!!.cacheDir, CRASH_DIR_NATIVE)
        if (!nativeCrashFile.exists()) {
            nativeCrashFile.mkdirs()
        }
        return nativeCrashFile
    }

    fun crashFiles(): Array<File> {
        return getJavaCrashDir().listFiles() + getNativeCrashDir().listFiles()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/245437
推荐阅读
相关标签
  

闽ICP备14008679号