Android中Crash的记录和防护
1、崩溃的记录:
创建自定义UncaughtExceptionHandler处理异常
01.定义并设置DefaultUncaughtExceptionHandler
02.存在Throwable时记录异常信息saveCrashInfoToFile
03.异常情况killProcess进程,App强制退出
/**
* 异常处理捕获类
*/
@SuppressLint("StaticFieldLeak")
object CrashHandler : Thread.UncaughtExceptionHandler {
private val mLogFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault())
var path = "/sdcard/corelog/Crash/"
private var mContext: Context? = null
private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null
private val infos = HashMap<String, String>()
init {
//获取线程中的DefaultUncaughtExceptionHandler实例
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
//设置当前的DefaultUncaughtExceptionHandler
Thread.setDefaultUncaughtExceptionHandler(this)
}
/**
* 初始化
* @param context
*/
fun init(context: Context) {
mContext = context
//设置崩溃日志文件的存储路径
CrashHandler.path = context.externalCacheDir?.absolutePath.toString()
/**
* 确保创建文件夹
*/
val crashFolder = File(path)
if (!crashFolder.exists()) {
crashFolder.mkdirs()
}
}
/**
* 当发生uncaughtException异常时,会进入该方法进行处理
* @param thread
* @param throwable
*/
override fun uncaughtException(thread: Thread, throwable: Throwable) {
if (!handleException(throwable) && mDefaultHandler != null) {
mDefaultHandler!!.uncaughtException(thread, throwable)
} else {
try {
//异常线程延迟0.5s,用于handleException()方法中的异常信息记录
Thread.sleep(500)
} catch (e: InterruptedException) {
//记录异常信息
e.printStackTrace()
}
}
//发生异常,杀死当前进程
android.os.Process.killProcess(android.os.Process.myPid())
//退出当前System
System.exit(1)
}
/**
*
* @param throwable
* @return 没有处理返回false ,处理了返回true
*/
private fun handleException(throwable: Throwable?): Boolean {
//不存在异常
if (throwable == null) {
return false
}
//存在异常
collectDeviceInfo() // 收集设备参数信息
saveCrashInfoToFile(throwable) // 保存日志文件
return true
}
/**
* 获取崩溃的设备信息
*/
private fun collectDeviceInfo() {
try {
val pm = mContext!!.packageManager
val pi = pm.getPackageInfo(
mContext!!.packageName,
PackageManager.GET_ACTIVITIES
)
if (pi != null) {
val versionName = pi.versionName
val versionCode = pi.versionCode.toString() + ""
infos["versionName"] = versionName
infos["versionCode"] = versionCode
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
val fields = Build::class.java.declaredFields
for (field in fields) {
try {
field.isAccessible = true
infos[field.name] = field.get(null).toString()
Logcat.d("crash", field.name + " : " + field.get(null))
} catch (e: Exception) {
e.printStackTrace()
}
}
}
/**
* 保存崩溃信息到指定路径
*
* @param throwable
*/
private fun saveCrashInfoToFile(throwable: Throwable) {
// 获取设备信息
val sb = StringBuffer()
for ((key, value) in infos) {
sb.append("$key=$value\n")
}
// 获取崩溃信息
val writer = StringWriter()
val printWriter = PrintWriter(writer)
throwable.printStackTrace(printWriter)
var cause: Throwable? = throwable.cause
// 循环着把所有异常信息写入writer中
while (cause != null) {
cause.printStackTrace(printWriter)
cause = cause.cause
}
printWriter.close()
val result = writer.toString()
sb.append(result)
// 获取日志
var buffRead: BufferedReader? = null
var str: String? = null
try {
/* 命令的准备 */
val getLog = ArrayList<String>()
getLog.add("logcat")
getLog.add("-t")
getLog.add("1200")
getLog.add("-v")
getLog.add("time")
val clearLog = ArrayList<String>()
clearLog.add("logcat")
clearLog.add("-c")
val process = Runtime.getRuntime().exec(
getLog.toTypedArray()
)// 抓取当前的缓存日志
buffRead = BufferedReader(
InputStreamReader(process.inputStream)
)
/**
* 获取输入流
*/
Runtime.getRuntime().exec(clearLog.toTypedArray())
/**
* 清除是为了下次抓取不会从头抓取
*/
} catch (e1: IOException) {
e1.printStackTrace()
}
// 保存崩溃信息
val time = mLogFormat.format(Date())
val fileName = getNewName(time)
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
val file = File(path)
if (!file.exists()) {
file.mkdirs()
}
val fos: FileOutputStream
try {
/**
* 输入文件
*/
fos = FileOutputStream("$path/$fileName")
fos.write(sb.toString().toByteArray())
val newline = System.getProperty("line.separator")// 换行的字符串
var loop = true
while (loop) {
var str = buffRead!!.readLine()
if (str != null) {
fos.write((time + str!!).toByteArray())// 加上年
fos.write(newline!!.toByteArray())// 换行
} else {
loop = false
}
}
fos.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
/**
* 新文件
* @param tim
* @return
*
*
* 默认保留20个崩溃文件超过20个
* 从后往前删
*/
private fun getNewName(time: String): String {
val dirFile = File(path)
//删除
if (dirFile != null) {
val mFiles = dirFile.listFiles()
if (mFiles != null && mFiles.size > 20) {
var deleteFile: File? = null
val localTime = System.currentTimeMillis()
//先从当前时间往后找,最近的一个
for (mFile in mFiles) {
if (mFile.lastModified() > localTime) {
if (deleteFile == null) {
deleteFile = mFile
} else if (mFile.lastModified() < deleteFile.lastModified()) {
deleteFile = mFile
}
}
}
/**
* 没有,则从前往后找最早的一个
*/
if (deleteFile == null) {
deleteFile = mFiles[0]
for (mFile in mFiles) {
if (mFile.lastModified() < deleteFile!!.lastModified()) {
deleteFile = mFile
}
}
}
deleteFile?.delete()
}
}
val timeStamp = System.currentTimeMillis()
val name = "LOG-$time-$timeStamp"
return "$name.log"
}
}
2、崩溃的捕获:
创建NeverCrash来try/catch所有线程的异常
01.try/catch所有的异常过程,让异常无法抛出
02.打印异常信息
/**
* 异常捕获
*/
public class NeverCrash {
private CrashHandler mCrashHandler;
private static NeverCrash mInstance;
private NeverCrash() {
}
private static NeverCrash getInstance() {
if (mInstance == null) {
synchronized (NeverCrash.class) {
if (mInstance == null) {
mInstance = new NeverCrash();
}
}
}
return mInstance;
}
public static void init(CrashHandler crashHandler) {
getInstance().setCrashHandler(crashHandler);
}
private void setCrashHandler(final CrashHandler crashHandler) {
mCrashHandler = crashHandler;
//设置捕获所有的异常,下面定义的DefaultUncaughtExceptionHandle就无法获取并处理异常信息了
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
for (; ; ) {
try {
//try-catch掉所有的异常,异常不会抛出,App不会崩溃
Looper.loop();
} catch (Throwable e) {
if (mCrashHandler != null) {
//捕获异常处理。自定义的MyApplication中实现了异常处理的具体方法
mCrashHandler.uncaughtException(Looper.getMainLooper().getThread(), e);
}
}
}
}
});
// //设置静态的默认的UncaughtExceptionHandler,管辖所有进程的异常信息
// Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
// @Override
// public void uncaughtException(Thread t, Throwable e) {
// //打印异常信息,不处理异常,所以出现崩溃仍然会退出App
// if (mCrashHandler != null) {
// //捕获异常处理
// mCrashHandler.uncaughtException(t, e);
// }
//
// }
// });
}
public interface CrashHandler {
void uncaughtException(Thread t, Throwable e);
}
//异常拦截器
public static boolean crashInterceptor(Throwable throwable, Thread thread) {
if (thread.getId() == 1
|| throwable == null
|| throwable.getMessage() == null
|| throwable.getStackTrace() == null) {
//异常发生之后,所在线程会挂掉.所以主线程异常,拦截了也没用.主线程也会死掉.
//除非,后续判断,app在前台,触发APP重启.
return false;
}
String classpath = null;
if (throwable.getStackTrace() != null && throwable.getStackTrace().length > 0) {
classpath = throwable.getStackTrace()[0].toString();
}
if (classpath == null) {
return false;
}
//拦截GMS异常.
if (throwable.getMessage().contains("Results have already been set") && classpath.contains(
"com.google.android.gms")) {
return true;
}
//拦截GMS的 NPE.
if (classpath.contains("com.google.android.gms") && throwable instanceof NullPointerException) {
return true;
}
//拦截ssl_NPE
if (throwable instanceof NullPointerException && throwable.getMessage()
.contains("ssl_session == null")) {
return true;
}
return false;
}
}
设置NeverCrash的初始化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
NeverCrash.init { t, e -> Log.i("hy55", "t.name = ${t.name} + e.message = ${e.message}") }
}
}
3、源码分析:
01.Thread.java中定义了处理异常接口UncaughtExceptionHandler @FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
02.ThreadGroup中定义了处理异常的流程:子线程的异常会让父线程来进行处理
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
03.RuntimeInit.java中定义了异常的具体处理方法
//定义了异常的处理类DefaultUncaughtExceptionHandler
@UnsupportedAppUsage
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
......
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
// Try to end profiling. If a profiler is running at this point, and we kill the
// process (below), the in-memory buffer will be lost. So try to stop, which will
// flush the buffer. (This makes method trace profiling useful to debug crashes.)
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// Bring up crash dialog, wait for it to be dismissed
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
//杀死当前进程,App强制退出
Process.killProcess(Process.myPid());
System.exit(10);
}
}
4、崩溃的感悟:
-
01.Thread中UncaughtExceptionHandler用于处理异常,当异常发生且未捕获时,UncaughtExceptionHandler会抛出异常,并且该线程会消亡
02.所以在Android中子线程死亡是允许的,主线程死亡就会导致ANR
03.防止异常而不能只是捕获异常
04.参考文章:https://blog.youkuaiyun.com/jamin0107/article/details/78865442