一个项目上线总会遇到各种各样未被预测的bug导致app莫名闪退,更何况在android这个机型满天飞的操作系统下。
好今天给大家讲解下android全局异常捕获的处理方案,让你完全掌控每个用户每个机型所发生的闪退原因!
首先大家先来了解下Thread.UncaughtExceptionHandler:
/**
* Interface for handlers invoked when a <tt>Thread</tt> abruptly
* terminates due to an uncaught exception.
* <p>When a thread is about to terminate due to an uncaught exception
* the Java Virtual Machine will query the thread for its
* <tt>UncaughtExceptionHandler</tt> using
* {@link #getUncaughtExceptionHandler} and will invoke the handler's
* <tt>uncaughtException</tt> method, passing the thread and the
* exception as arguments.
* If a thread has not had its <tt>UncaughtExceptionHandler</tt>
* explicitly set, then its <tt>ThreadGroup</tt> object acts as its
* <tt>UncaughtExceptionHandler</tt>. If the <tt>ThreadGroup</tt> object
* has no
* special requirements for dealing with the exception, it can forward
* the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
* default uncaught exception handler}.
*
* @see #setDefaultUncaughtExceptionHandler
* @see #setUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
@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)
;
}
通过源码可以知道: Thread.UncaughtExceptionHandler是一个当线程由于未捕获的异常突然终止而调用处理程序的接口. 当线程由于未捕获的异常即将终止时,Java虚拟机将使用它来查询其UncaughtExceptionHandler的线程Thread.getUncaughtExceptionHandler(),并将调用处理程序的 uncaughtException方法,将线程和异常作为参数传递。如果一个线程没有显示它的UncaughtExceptionHandler,那么它的ThreadGroup对象充当它的 UncaughtExceptionHandler。如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发到默认的未捕获的异常处理程序。
因此我们可以自定一个类去实现该接口,该类主要用于收集错误信息和保存错误信息.
代码实现:
package demo.com.androidglobalexception
;
import android.app.Application
;
import android.content.Context
;
import android.content.pm.PackageInfo
;
import android.content.pm.PackageManager
;
import android.os.Build
;
import android.os.Environment
;
import android.util.Log
;
import java.io.File
;
import java.io.FileOutputStream
;
import java.io.PrintWriter
;
import java.io.StringWriter
;
import java.io.Writer
;
import java.lang.reflect.Field
;
import java.text.DateFormat
;
import java.text.SimpleDateFormat
;
import java.util.Date
;
import java.util.HashMap
;
import java.util.Map
;
/**
* <p>名称:CrashHandler<p>
* <p> 描述:闪退异常处理<p>
* @author guoweiquan
* @version 1.0
* @data 2018/5/18 上午9:22
*/
public class CrashHandler
implements Thread.UncaughtExceptionHandler{
public static final String
TAG =
"CrashHandler";
private static CrashHandler
instance =
new CrashHandler()
;
private Context
mContext;
private Application
app;
// 系统默认的 UncaughtException 处理类
private Thread.UncaughtExceptionHandler
mDefaultHandler;
// 用来存储设备信息和异常信息
private Map<String
, String>
infos =
new HashMap<String
, String> ()
;
// 用于格式化日期,作为日志文件名的一部分
private DateFormat
formatter =
new SimpleDateFormat (
"yyyy-MM-dd-HH-mm-ss")
;
public UpdataErrorInfoLinster
delegete;
private CrashHandler() {
}
public static CrashHandler
getInstance() {
return instance;
}
/**
* @Title: init
* @Description: 初始化
* @param context
* @param app
* 传入的app
* @throws
*/
public void init(Context context
, Application app
, UpdataErrorInfoLinster l) {
this.
app = app
;
mContext = context
;
this.
delegete = l
;
// 获取系统默认的 UncaughtException 处理器
mDefaultHandler = Thread.
getDefaultUncaughtExceptionHandler()
;
// 设置该 CrashHandler 为程序的默认处理器
Thread.
setDefaultUncaughtExceptionHandler(
this)
;
}
/**
* 当 UncaughtException 发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread
, Throwable ex) {
if (!handleException(ex) &&
mDefaultHandler !=
null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread
, ex)
;
}
else {
app.onTerminate()
;
}
}
/**
* 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
*
* @param ex
* @return true:如果处理了该异常信息;否则返回 false
*/
private boolean handleException(Throwable ex) {
if (ex ==
null) {
return false;
}
// 收集设备参数信息
collectDeviceInfo(
mContext)
;
// 保存日志文件
saveCrashInfo2File(ex)
;
return true;
}
/**
* 收集设备参数信息
*
* @param ctx
*/
public void collectDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager()
;
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName()
,
PackageManager.
GET_ACTIVITIES)
;
if (pi !=
null) {
String versionName = pi.
versionName ==
null ?
"null"
: pi.
versionName;
String versionCode = pi.
versionCode +
"";
infos.put(
"versionName", versionName)
;//版本名称
infos.put(
"versionCode", versionCode)
;//版本号
}
}
catch (PackageManager.NameNotFoundException e) {
Log.
e(
TAG, "an error occured when collect package info", e)
;
}
Field[] fields = Build.
class.getDeclaredFields()
;
for (Field field : fields) {
try {
field.setAccessible(
true)
;
infos.put(field.getName()
, field.get(
null).toString())
;
}
catch (Exception e) {
Log.
e(
TAG, "an error occured when collect crash info", e)
;
}
}
}
/**
* 保存错误信息(本地存储和回调处理:比如发给服务器)
*
* @param ex
* @return 返回文件名称,便于将文件传送到服务器
*/
private String
saveCrashInfo2File(Throwable ex) {
StringBuffer sb =
new StringBuffer ()
;
for (Map.Entry<String
, String> entry :
infos.entrySet()) {
String key = entry.getKey()
;
String value = entry.getValue()
;
sb.append(key +
"=" + value +
"\n")
;
}
Writer writer =
new StringWriter ()
;
PrintWriter printWriter =
new PrintWriter (writer)
;
ex.printStackTrace(printWriter)
;
Throwable cause = ex.getCause()
;
while (cause !=
null) {
cause.printStackTrace(printWriter)
;
cause = cause.getCause()
;
}
printWriter.close()
;
String result = writer.toString()
;
sb.append(result)
;
Log.
e(
TAG,sb.toString())
;//答应错误信息
/******************************/
//上传服务器
delegete.onUpdataErrorinfo(sb.toString())
;
String filePath =
"";
try {
long timestamp = System.
currentTimeMillis()
;
String time =
formatter.format(
new Date ())
;
String fileName =
"error-" + time +
"-" + timestamp +
".log";
if (Environment.
getExternalStorageState().equals(
Environment.
MEDIA_MOUNTED)) {
String path = Environment.
getExternalStorageDirectory()
.getAbsolutePath() +
"/crashs";
// Toast.makeText(mContext, path, Toast.LENGTH_LONG).show();
File dir =
new File (path)
;
if (!dir.exists()) {
dir.mkdirs()
;
}
filePath = path + fileName
;
FileOutputStream fos =
new FileOutputStream (path + fileName)
;
fos.write(sb.toString().getBytes())
;
fos.flush()
;
fos.close()
;
}
return filePath
;
}
catch (Exception e) {
Log.
e(
TAG, "an error occured while writing file...", e)
;
}
return null;
}
public interface UpdataErrorInfoLinster
{
void onUpdataErrorinfo(String str)
;
}
}
本类主要核心方法是
public void uncaughtException(Thread thread
, Throwable ex)
当系统捕获到异常时uncaughtException将会被调用,并把异常传递过来。
其他的方法就不多赘述注释都写的很明白。
重点讲下:
public interface UpdataErrorInfoLinster
{
void onUpdataErrorinfo(String str)
;
}
这个接口是将捕获的信息回调出去的。
代理者是:public UpdataErrorInfoLinster delegete;
详细使用:
package demo.com.androidglobalexception
;
import android.app.Application
;
import android.util.Log
;
/**
* <p>描述:<p>
*
* @author guoweiquan
* @version 1.0
* @data 2018/5/18 上午9:22
*/
public class MyApp
extends Application {
public static String
TAG =
"MyApp";
@Override
public void onCreate() {
super.onCreate ()
;
Log.
e(
TAG,"onCreate is running")
;
CrashHandler crashHandler = CrashHandler.
getInstance()
;
crashHandler.init(getApplicationContext()
, this ,new CrashHandler.UpdataErrorInfoLinster()
{
@Override
public void onUpdataErrorinfo(String str) {
//发送错误信息给服务器
Log.
e(
"TAG","exception is-->" + str)
;
}
})
;
int a =
1/
0 ;
}
}
在MyApp类中创建CrashHandler对象并初始化设置回调对象。
然后在 onUpdataErrorinfo(String str)方法中获取到excaption信息,然后就可以按照业务需求处理,比如发送给服务器,那出现闪退就可以在线分析了。
OK,就这么简单;
项目地址:https://github.com/seaeel/AndroidGloadException.git
博主技术交流QQ群:239025382