赞
踩
在应用启动及运行中,出现闪退(崩溃),屏幕提示当前程序停止运行的弹窗,类似于windows的应用程序崩溃。
概括来讲,就是程序运行中有未捕获的异常,未被 try-catch,导致进程被杀。
Thread
的 dispatchUncaughtException
方法把异常传递给线程的未捕获异常处理器。defaultUncaughtExceptionHandler
来处理异常。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; } }
一旦线程出现未捕获的异常,JVM将调用Thread
的 dispatchUncaughtException
方法将异常传递给线程的未捕获的异常处理器。
如果没有设置 UncaughtExceptionHandler
,将使用线程所在的线程组来处理这个未捕获的异常
线程 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); } } }
RuntimeInit#main
方法入口函数中,调用了 commonInit
方法设置了默认的异常处理器。public class RuntimeInit {
protected static final void commonInit() {
//在这里通过静态调用 为线程设置了默认的异常处理器。
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
}
}
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异常处理,记录当前设备的基础信息,及线程错误信息存储到本地文件中。在需要的时候上传到服务器。
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分钟内系统出现两次 crash 就不在重启了,否则这个应用被卸载无疑。
**工具:**在SDK的的目录下:D:\software_for_code\Android\SDK\tools\proguard\bin
使用方法:
mapping file
文件中选择: mapping.txt
,前提是代码开了混淆。将要还原的代码复制到窗口中。ReTrace
即可。native_crash
,就是 native 层发生了 crash。内部是通过一个 NativeCrashListener
线程去监控的。class ActivityManagerService{
public void startObservingNativeCrashes() {
final NativeCrashListener ncl = new NativeCrashListener(this);
ncl.start();
}
}
直接引入第三方库来处理 native 异常
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() } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。