Android中Crash原理及监控处理

什么是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. 一旦线程出现未捕获的异常,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. RuntimeInit#main 方法入口函数中,调用了 commonInit 方法设置了默认的异常处理器。
public class RuntimeInit {

    protected static final void commonInit() {
        //在这里通过静态调用 为线程设置了默认的异常处理器。
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    }
}
  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);
        }
    }
}

其实在 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()
    }
}

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();
    }
}

直接引入第三方库来处理 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()
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Android原生崩溃(android native crash)是指在Android平台上,由于代码执行错误或者资源耗尽等原因,导致应用程序无法正常运行而崩溃或者闪退的现象。原生崩溃产生的原因可能是由于C或者C++代码编写错误、内存溢出、线程竞争等。针对原生崩溃问题,开发人员需要使用调试工具进行定位和修复。 ### 回答2: Android Native Crash发生在安卓应用程序运行时,由于C或C++库的错误或者其他原因导致应用程序崩溃。有时候Native Crash可能会影响整个设备,尤其是当Native Crash发生在系统级别的代码时。 产生Native Crash的原因通常包括以下几个方面: 1. 内存管理问题:Native Crash通常与内存管理问题相关,这可能是由于访问未初始化的内存,使用错误的指针或释放已释放的内存等原因引起的。 2. 硬件问题:Native Crash也可能与设备相关的硬件问题有关,例如访问不可用的硬件资源或硬件设备故障。 3. 应用程序代码问题:Native Crash可能发生在应用程序代码的错误、资源泄漏、堆栈溢出等问题引起的。 4. 第三方库问题:Native Crash也可能由第三方库的错误或bug引起。这些库可能没有经过充分的测试,或者与设备硬件不兼容。 为了更好地解决Native Crash问题,开发者可以通过日志或崩溃报告(Crash Report)来检测和分析崩溃日志,并查看堆栈跟踪信息来确定导致Native Crash的来源。在开发过程,经常使用除了自己编写的代码之外的第三方库时,还可以考虑使用崩溃的回溯工具,如Firebase Crashlytics等。 总之,Native CrashAndroid应用程序开发过程经常遇到的问题,它可能会对用户体验和开发进度产生重大影响,因此开发者需要强化对Native Crash的理解和分析能力,以更好地解决Native Crash的问题。 ### 回答3: Android Native Crash,指的是在 Android 系统发生的本地崩溃。本地崩溃是指应用程序使用本地代码,而不是 Java 代码,导致应用程序崩溃的问题。本地代码可以是编写在 C/C++ 等语言的库,或是应用程序本身所编写的 Native 代码。 本地代码崩溃后,会在应用程序崩溃的同时发生。本地崩溃可发生在 Android 应用程序的任何部分,比如,应用程序本地库、Android 系统库等等。大多数情况下,本地崩溃是由于访问无效内存、访问不合法指针、数组越界等问题引起的。 为了解决本地崩溃问题,Android 提供了一些工具和技术。比如,使用 ndk-stack 工具可以解析本地崩溃日志。Android Studio 也提供了一些工具来分析应用程序崩溃的原因。同时,我们也可以在应用程序添加自定义的日志跟踪信息,以便更好地了解应用程序的崩溃原因。 还有一些其他的技术可以使用,如使用 Google 的 Crashlytics 来跟踪应用程序的崩溃问题。这个平台可以帮助开发者收集和分析应用程序在用户设备上的崩溃信息,并彻底解决这些问题。此外,Android 还提供了一些实用工具和技术,如 ANR(Application Not Responding)错误处理器、Tracer for OpenGL ES 和 Traceview 示例等。 总之,Android Native CrashAndroid 系统常见的崩溃问题之一。了解它的原因并采用适当的解决方案可以使得我们更好地保持我们的应用程序的稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值