Thread.UncaughtExceptionHandler
UncaughtExceptionHandler(未捕获异常处理器)是Thread类的静态内部接口,用来处理用户没有try…caught的异常。也就是系统运行出错throw出来的异常。
UncaughtExceptionHandler里面只有一个方法:
void uncaughtException(Thread thread, Throwable ex);
实现原理
用户调用Thread的静态方法 setDefaultUncaughtExceptionHandler,将自己实现的未捕获异常处理类 设置到Thread私有的静态成员属性defaultUncaughtHandler,也就是简单的set过程。
private static UncaughtExceptionHandler defaultUncaughtHandler;
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
Thread.defaultUncaughtHandler = handler;
}
如果有用户未捕获的异常信息抛出,那么Thread就会调用其成员属性defaultUncaughtHandler的方法uncaughtException 并传入发生异常的thread和ex异常事件。
So what
那这货有什么用呢?
作为一个用户体验友好的app,应该在未知异常发生时给予用户一定的提示,或者主页面跳转。而不是直接直接奔溃闪退。再者,当我们把app上架到应用市场后,应具备奔溃日志收集功能,这样就可以在查看各个不同设备发生异常的情况,并在下一版本中改善这些情况。
好,那看代码就能知道怎个过程的始末了。
code
/**
* Created by cchao on 2016/9/2.
* E-mail: cchao1024@163.com
* Description:奔溃日志收集
*/
public class CrashCatchHandler implements Thread.UncaughtExceptionHandler {
public static final String TAG = "CrashCatchHandler";
//log保存路径
public static String LOG_PATH;
private Context mContext;
//CrashCatchHandler单例
private static CrashCatchHandler INSTANCE = new CrashCatchHandler ( );
//存储设备信息和异常信息
private Map< String, String > mInfoMap;
private SimpleDateFormat mDateFormat;
private CrashCatchHandler ( ) {
mInfoMap = new LinkedHashMap<> ( );
mDateFormat = new SimpleDateFormat ( "yyyy-MM-dd-HH-mm-ss" );
}
/**
* 获取CrashCatchHandler实例 ,单例模式
*/
public static CrashCatchHandler getInstance ( ) { return INSTANCE; }
/**
* 初始化
*
* @param context
*/
public void init ( Context context ) {
mContext = context;
//设置该CrashCatchHandler为程序的默认未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler ( this );
//如果外存卡可以读写,放在外部存储器,否则放在内部存储器上
if ( Environment.getExternalStorageState ( ).equals ( Environment.MEDIA_MOUNTED ) ) {
LOG_PATH = mContext.getExternalFilesDir ( "CARCH_LOG" ).getPath ( );
} else {
LOG_PATH = mContext.getFilesDir ( ).getPath ( );
}
}
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException ( Thread thread, Throwable ex ) {
Toast.makeText ( mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG ).show ( );
//收集设备参数信息
collectDeviceInfo ( mContext );
//保存日志文件至本地
postService ( saveCrashLog ( ex ) );
}
/**
* 发送错误日志至服务端
*
* @param fileName log路径
*/
private void postService ( String fileName ) {
if ( fileName != null ) {
//发送奔溃日志 至服务器
// TODO: 2016/9/2
}
}
/**
* 收集奔溃设备参数信息
*/
public void collectDeviceInfo ( Context context ) {
try {
PackageManager pm = context.getPackageManager ( );
PackageInfo packageInfo = pm.getPackageInfo ( context.getPackageName ( ), PackageManager.GET_ACTIVITIES );
if ( packageInfo != null ) {
String appName = packageInfo.applicationInfo.packageName;
String versionName = packageInfo.versionName + "";
String versionCode = packageInfo.versionCode + "";
mInfoMap.put ( "包名", appName );
mInfoMap.put ( "版本名", versionName );
mInfoMap.put ( "版本号", versionCode );
}
} catch ( PackageManager.NameNotFoundException e ) {
Log.e ( TAG, "收集设备信息出错", e );
}
Field[] fields = Build.class.getDeclaredFields ( );
for ( Field field : fields ) {
try {
field.setAccessible ( true );
mInfoMap.put ( field.getName ( ), field.get ( null ).toString ( ) );
} catch ( Exception e ) {
Log.e ( TAG, "收集奔溃日志出错", e );
}
}
}
/**
* 保存错误信息到文件中
*
* @param ex
* @return 返回文件名称
*/
private String saveCrashLog ( Throwable ex ) {
StringBuilder stringBuilder = new StringBuilder ( );
for ( Map.Entry< String, String > entry : mInfoMap.entrySet ( ) ) {
String key = entry.getKey ( );
String value = entry.getValue ( );
stringBuilder.append ( key + " = " + value + "\n" );
}
//异常写入stringBuilder
Writer writer = new StringWriter ( );
PrintWriter printWriter = new PrintWriter ( writer );
ex.printStackTrace ( printWriter );
//异常原因写入stringBuilder
Throwable cause = ex.getCause ( );
if ( cause != null ) {
cause.printStackTrace ( printWriter );
}
printWriter.close ( );
String result = writer.toString ( );
stringBuilder.append ( result );
try {
String time = mDateFormat.format ( new Date ( System.currentTimeMillis ( ) ) );
String fileName = time + ".txt";
File dir = new File ( LOG_PATH );
if ( ! dir.exists ( ) ) {
dir.mkdirs ( );
}
FileOutputStream fos = new FileOutputStream ( dir.getAbsolutePath ( ) + File.separator + fileName );
fos.write ( stringBuilder.toString ( ).getBytes ( ) );
fos.close ( );
return fileName;
} catch ( Exception e ) {
Log.e ( TAG, "写入文件异常", e );
}
return null;
}
}
异常保存的本地路径是:Android/data/包名/files/CARCH_LOG/时间+.txt
在Application中去调用我们自定义的UncaughtExceptionHandler。
public class BaseApplication extends Application {
@Override
public void onCreate ( ) {
super.onCreate ( );
CrashCatchHandler.getInstance().init(getApplicationContext ());
}
}