转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/112476811
本文出自【赵彦军的博客】

文章目录

  • 前言
  • Thread.dumpStack()
  • 如何把线程堆栈日志保存到文件
  • 如何捕捉Crash
  • 异常传递
  • 惊喜
  • 扩展设备信息

前言

因为疫情原因,今年的年会取消了,对于这个年会期待已久,心里还是有点失落。疫情当前,祝福所有人平安。

本文所有代码示例都上传至:https://github.com/zyj1609wz/AndroidCrash

Thread.dumpStack()

打印当前线程调用堆栈, 这个在调试时特别好用,举例如下:

Util.java

public class Util {public static void print(){Thread.dumpStack();}
}

MainActivity.java

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Util.print()}
}

效果如下:

看到这个日志,你想到什么? 肯定是想到崩溃日志,是吧?

线程堆栈日志清晰的标明了当前发生的类及其行号,更重要的是显示了方法调用路径。这个很重要,对于排查问题,调试项目提供了很好的帮助。

下次遇到问题需要调试时,可以试试这个方法,很有用?

如何把线程堆栈日志保存到文件

我们先看看 Thread.dumpStack() 源码


很简单,其实就是调用了 ThrowableprintStackTrace 方法。

除此之外, Throwable 还有一个方法,允许外部传入一个 PrintWriter


完整的代码如下:

package com.cootek.remoteapp;import android.content.Context;
import android.os.Environment;import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Locale;/*** @author yanjun.zhao* @time 2021/1/11 4:44 PM* @desc*/
public class Util {private static final Format FORMAT = new SimpleDateFormat("MM-dd HH-mm-ss", Locale.getDefault());/*** 打印当前线程堆栈,保存到本地文件** @param con*/public static void print(Context con) {Context context = con.getApplicationContext();String fileName = getFileDir(context) + FORMAT.format(System.currentTimeMillis()) + ".txt";if (createOrExistsFile(fileName)) {PrintWriter pw = null;try {pw = new PrintWriter(new FileWriter(fileName, false));new Exception("Stack trace").printStackTrace(pw);} catch (IOException e) {e.printStackTrace();} finally {if (pw != null) {pw.close();}}}}/*** 创建文件* @param filePath* @return*/private static boolean createOrExistsFile(String filePath) {File file = new File(filePath);if (file.exists()) {return file.isFile();}if (!createOrExistsDir(file.getParentFile())) {return false;}try {return file.createNewFile();} catch (IOException e) {e.printStackTrace();return false;}}private static boolean createOrExistsDir(File file) {return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());}/*** 获取堆栈日志存储目录** @param context* @return*/private static String getFileDir(Context context) {if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())&& context.getExternalCacheDir() != null) {return context.getExternalCacheDir() + File.separator + "crash" + File.separator;} else {return context.getCacheDir() + File.separator + "crash" + File.separator;}}
}

MainActivity 如下:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Util.print(this)}
}

允许起来,看一下效果:


可以看到文件已经写入了,打开看看 :

很完美啊 !!!

如何捕捉Crash

没有 try…catch 住的异常,即 Uncaught 异常,都会导致应用程序崩溃。那么面对崩溃,我们是否可以做些什么呢?比如程序退出前,弹出个性化对话框,而不是默认的强制关闭对话框,或者弹出一个提示框安慰一下用户,甚至重启应用程序等。

其实Java提供了一个接口给我们,可以完成这些,这就是 UncaughtExceptionHandler,该接口含有一个纯虚函数:public abstract void uncaughtException (Thread thread, Throwableex)

Uncaught 异常发生时会终止线程,此时,系统便会通知 UncaughtExceptionHandler ,告诉它被终止的线程以及对应的异常,然后便会调用 uncaughtException 函数。如果该 handler 没有被显式设置,则会调用对应线程组的默认 handler 。如果我们要捕获该异常,必须实现我们自己的handler,并通过以下函数进行设置:

public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler)

实现自定义的 handler,只需要继承UncaughtExceptionHandler该接口,并实现uncaughtException方法即可。

package com.cootek.remoteapp;import android.content.Context;
import android.os.Environment;import androidx.annotation.NonNull;import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Locale;/*** @author yanjun.zhao* @time 2021/1/11 4:44 PM* @desc*/
public class CrashUtil implements Thread.UncaughtExceptionHandler {private static final Format FORMAT = new SimpleDateFormat("MM-dd HH-mm-ss", Locale.getDefault());private Context mContext;private static CrashUtil INSTANCE = new CrashUtil();/*** 保证只有一个CrashHandler实例*/private CrashUtil() {}/*** 获取CrashHandler实例 ,单例模式*/public static CrashUtil getInstance() {return INSTANCE;}public void init(Context context) {this.mContext = context.getApplicationContext();//这一句,至关重要,一定要设置 Thread.setDefaultUncaughtExceptionHandler(this);}/*** 打印当前线程堆栈,保存到本地文件*/public void print(Throwable throwable) {String fileName = getFileDir(mContext) + FORMAT.format(System.currentTimeMillis()) + ".txt";if (createOrExistsFile(fileName)) {PrintWriter pw = null;try {pw = new PrintWriter(new FileWriter(fileName, false));throwable.printStackTrace(pw);} catch (IOException ioException) {} finally {if (pw != null) {pw.close();}}}}/*** 创建文件** @param filePath* @return*/private static boolean createOrExistsFile(String filePath) {File file = new File(filePath);if (file.exists()) {return file.isFile();}if (!createOrExistsDir(file.getParentFile())) {return false;}try {return file.createNewFile();} catch (IOException e) {e.printStackTrace();return false;}}private static boolean createOrExistsDir(File file) {return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());}/*** 获取堆栈日志存储目录** @param context* @return*/private static String getFileDir(Context context) {if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())&& context.getExternalCacheDir() != null) {return context.getExternalCacheDir() + File.separator + "crash" + File.separator;} else {return context.getCacheDir() + File.separator + "crash" + File.separator;}}@Overridepublic void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {print(e);}
}

MyApp 代码:

/*** @author yanjun.zhao* @time 2021/1/11 7:52 PM* @desc*/
public class MyApp : Application() {override fun onCreate() {super.onCreate()CrashUtil.getInstance().init(this)}
}

主要代码,我们就写完了,下面我们来测试一下:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)findViewById<TextView>(R.id.bt).setOnClickListener {//制造一个crash5 / 0}}
}

允许起来,点击 button , 人为制造一个 crash ,发现 Android 应用程序没有崩溃,再点开crash 日志目录,发现已经生成了日志,打开看看:

异常传递

在上面的例子中,我们人为的制造了一个 crash , 并且成功的捕捉了,把 crash 日志写入本地文件。

一个直观的感觉是:app 不会崩溃了。

但是也有一个问题,其他 crash 捕捉器就捕捉不到了,比如 :bugly 。如何才能解决这个问题。

第一步,在 Thread.setDefaultUncaughtExceptionHandler 之前,先获取 defaultUncaughtExceptionHandler

 public void init(Context context) {this.mContext = context.getApplicationContext();defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();Thread.setDefaultUncaughtExceptionHandler(this);
}

在处理异常的地方,先处理自己的逻辑,然后把异常向后传递

@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {//优先处理自己的逻辑,把crash日志存起来print(e);if (defaultUncaughtExceptionHandler != null) {//如果原来的 Thread 有自己的 handler , 就把 crash 传递下去,//比如:如果集成了bugly , 那就传给bugly 处理defaultUncaughtExceptionHandler.uncaughtException(t, e);} else {}
}

完整的 CrashUtil 类如下:

package com.cootek.remoteapp;import android.content.Context;
import android.os.Environment;import androidx.annotation.NonNull;import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Locale;/*** @author yanjun.zhao* @time 2021/1/11 4:44 PM* @desc*/
public class CrashUtil implements Thread.UncaughtExceptionHandler {private static final Format FORMAT = new SimpleDateFormat("MM-dd HH-mm-ss", Locale.getDefault());private Context mContext;private static CrashUtil INSTANCE = new CrashUtil();private Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;/*** 保证只有一个CrashHandler实例*/private CrashUtil() {}/*** 获取CrashHandler实例 ,单例模式*/public static CrashUtil getInstance() {return INSTANCE;}public void init(Context context) {this.mContext = context.getApplicationContext();defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();Thread.setDefaultUncaughtExceptionHandler(this);}/*** 打印当前线程堆栈,保存到本地文件*/public void print(Throwable throwable) {String fileName = getFileDir(mContext) + FORMAT.format(System.currentTimeMillis()) + ".txt";if (createOrExistsFile(fileName)) {PrintWriter pw = null;try {pw = new PrintWriter(new FileWriter(fileName, false));throwable.printStackTrace(pw);} catch (IOException ioException) {} finally {if (pw != null) {pw.close();}}}}/*** 创建文件** @param filePath* @return*/private static boolean createOrExistsFile(String filePath) {File file = new File(filePath);if (file.exists()) {return file.isFile();}if (!createOrExistsDir(file.getParentFile())) {return false;}try {return file.createNewFile();} catch (IOException e) {e.printStackTrace();return false;}}private static boolean createOrExistsDir(File file) {return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());}/*** 获取堆栈日志存储目录** @param context* @return*/private static String getFileDir(Context context) {if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())&& context.getExternalCacheDir() != null) {return context.getExternalCacheDir() + File.separator + "crash" + File.separator;} else {return context.getCacheDir() + File.separator + "crash" + File.separator;}}@Overridepublic void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {//优先处理自己的逻辑,把crash日志存起来print(e);if (defaultUncaughtExceptionHandler != null) {//如果原来的 Thread 有自己的 handler , 就把 crash 传递下去,//比如:如果集成了bugly , 那就传给bugly 处理defaultUncaughtExceptionHandler.uncaughtException(t, e);} else {}}
}

这样就同时兼容了 buglycrash捕捉工具。

惊喜

最近看到一个很出名的异常捕获工具,翻了它的源码,发现和我的做法一致,因此可以证明,我的做法没有问题。下面截一个图给大家看看,这个工具是怎么处理的?

扩展设备信息

bugly 一样,每一个 crash日志都会包含设备信息,app 版本号等。其实这个也很简单,把设备信息写入文件就行了,我在 github 上已经完善了,这里就不展示了。

这里展示一个完整的 crash日志:

Android 打造异常崩溃捕获工具相关推荐

  1. Android信息统计和崩溃收集工具

    Android信息统计和崩溃收集工具 用户行为数据收集 友盟 TalkingData Countly Flurry(Yahoo) Mixpanel Google Analytics app性能数据收集 ...

  2. android 捕捉 异常 崩溃 捕捉 crash

    转载时请记得标明源地址:http://my.oschina.net/lijindou/blog demo  的 源码 地址:http://pan.baidu.com/s/1mhDsJqg 大家应该 知 ...

  3. android 多线程 崩溃,Android异常崩溃捕获

    Android系统碎片化造成应用程序崩溃严重,在模拟器上运行良好的程序安装到某款手机上说不定就会出现崩溃的现象.而且,往往都是程序发布之后在用户端出现了崩溃现象.所以在程序发布出去之后,如果出现了崩溃 ...

  4. Android平台异常崩溃捕捉处理

    原文:https://blog.csdn.net/leeo1010/article/details/50522892 在我们Android开发的过程中,经常碰到app崩溃的状况.目前市面上各种各样的手 ...

  5. Android程序异常崩溃后重启

    有时候,我们需要应用在崩溃的时候自动重启,并打开崩溃前的那个Activity. 这时候,我们就需要用到Thread.UncaughtExceptionHandler这个接口. 首先,我们知道,既然是要 ...

  6. Android APP native 崩溃分析之 linker SIGBUS 崩溃

    原文地址:https://caikelun.io/post/2019-05-31-android-app-native-crash-linker-sigbus/ 这是 Android APP nati ...

  7. Android 平台的Crash崩溃捕获-全

    上层-java/kotlin: Android应用层java/kotlin的crash捕获相对容易.直接实现Thread.UncaughtExceptionHandler即可处理收集.Thread.U ...

  8. android 中处理崩溃异常并重启程序

    转:http://blog.csdn.net/cym_lmy/article/details/24704089 有时候由于测试不充分或者程序潜在的问题而导致程序异常崩溃,这个是令人无法接受的,在and ...

  9. Android记录日志方式,关于Android中处理崩溃异常和记录日志的另一种实现思路

    我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃.不过理想是丰满的,现实是骨感的.没有一个程序员能保证自己写的程序绝对不会出现异常崩溃.特别是针对用户数达到几十 ...

最新文章

  1. Visual C++ 2010中更换MFC对话框默认图标
  2. excel几个表合成一张_快速将多个excel表合并成一个excel表
  3. Fiddler抓取https相关设置
  4. 14-磁盘管理-df,du命令,磁盘分区
  5. ftp上传乱码_ftp上传与wordpres常规基本设置
  6. MySQL Root密码丢失解决方法总结
  7. 自定义获取焦点的TextView
  8. mro python_一窥Python中MRO排序原理
  9. 77GHz毫米波雷达快速chirp信号技术(二):测速原理
  10. ideal上初写mapreduce程序出现的报错信息解决
  11. Texture2D变Sprite
  12. 用户故事地图(User Story Mapping)之初体验
  13. 用JavaBean封装数据库操作
  14. 2022最全大数据学习路线(建议收藏)
  15. Java 悲观锁和乐观锁的实现
  16. 批量压缩多文件-批处理(四)
  17. CMakeLists学习二、链接库搜索路径与ld
  18. 比利时和德国啤酒品牌
  19. 来到fsb的第24天
  20. Python实现一键自动发送直播弹幕

热门文章

  1. 不安全的发布 java_如何在没有安全警告的情况下发布Java Web S...
  2. c语言二维图形变换程序,【计算机图形学】3-2 二维几何变换根本代码
  3. vo生成MySQL表_跟我学微服务统一开发平台-代码生成器
  4. Python只需要三十行代码,打造一款简单的人工语音对话
  5. 二、数据分析前,打下数据处理基础(上)
  6. 教师编学科知识计算机,教师考试信息技术学科知识考什么_谈信息技术学科教师应该怎样教学...
  7. 一文详解DeepMind最新模型SUNDAE,了解迭代去噪模型的前世今生
  8. ResNet才是YYDS!新研究:不用蒸馏、无额外数据,性能还能涨一波
  9. ICLR 2021 | 显存不够?不妨抛弃端到端训练
  10. 保姆式参赛教程全公开,居然还送10万奖金?