Android 难免有崩溃的时候,但是崩溃了该如何处理呢?虽然那天有位同仁说 “既然崩溃了,用户体验就差了,心里会想这是毛APP,下次也不想用了” ,所以检查BUG以防崩溃是必须的,但是也需要一个后备方案,崩溃了能友好些,我们也能收集一些崩溃的信息。
说到全局捕获异常的UncaughtExceptionHandler,就不得不说期间遇到的各种坑:
1. 初始化肯定在Application,网上说的Activity启各种不认同。但在Application启就存在不能弹AlertDialog的问题(目前不确定,不知道是自己哪里没处理好还是的确是这个问题,有时间再验证一下)
2. 崩溃不一定是单次,在多层Activity中,崩溃一个顶层的Activity可能导致下层的Activity连续崩溃,所以uncaughtException可能会捕获到多次崩溃信息(具体影响后面会说到)
先来张崩溃后的效果图:
背景是另一个APP,当前的APP已崩溃并弹出该提示
实现流程:
写个类继承于UncaughtExceptionHandler,实现方法
@Override
public void uncaughtException (Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null ) {
mDefaultHandler.uncaughtException(thread, ex);
} else {
Intent intent = new Intent(mContext, CrashDialog.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
System.exit(0 );
}
}
然后转handleException方法处理异常:
private boolean handleException (Throwable ex) {
if (ex == null ) {
return false ;
}
getCrashInfo(ex);
return true ;
}
上面的代码很清楚了,如果异常被捕获到并且异常信息不会NULL,处理完则跳转到CrashDialog。为什么跳Activity用Dialog样式,而不直接弹AlertDialog,是因为的确弹不出来。
收集错误信息:
private void getCrashInfo(Throwable ex) {
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 errorMessage = writer.toString ()
if (Environment.getExternalStorageState ().equals (Environment.MEDIA _MOUNTED)) {
String mFilePath = Environment.getExternalStorageDirectory () + "/" + App.ERROR _FILENAME
FileTxt.WirteTxt (mFilePath, FileTxt.ReadTxt (mFilePath) + '\n' + errorMessage)
} else {
Log.i (App.TAG , "哦豁,说好的SD呢..." )
}
}
是的,我把错误信息写到了存储并在刚才的CrashDialog中读取。为什么不直接传值呢?因为刚说到的坑第2条,多次崩溃的情况下,将导致直接传值只会传最后一次崩溃信息,而最后一次崩溃信息并不是主要引发崩溃的点,收集上来的错误信息可读性不大。那为什么我不写个全局变量来存储呢?因为尝试过,不知道是机型问题(Huawei Mate7 - API 23)还是全部问题,变量压根就不记录数据,最后只有将信息依次写到存储。
主要代码
App.java
package cn.qson.androidcrash;
/**
* @author x024
*/
import android.app.Application;
public class App extends Application {
public final static String TAG = "x024" ;
public final static String ERROR_FILENAME = "x024_error.log" ;
@Override
public void onCreate () {
super .onCreate();
CrashHanlder.getInstance().init(this );
}
}
CrashHanlder.java
package cn.qson.androidcrash;
/**
* @author x024
*/
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;
/**
* 收集错误报告并上传到服务器
*
* @author x024
*
*/
public class CrashHanlder implements UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler mDefaultHandler;
private static CrashHanlder INSTANCE = new CrashHanlder();
private Context mContext;
private CrashHanlder () {
}
public static CrashHanlder getInstance () {
return INSTANCE;
}
/**
* 初始化
*
* @param context
*/
public void init (Context context) {
mContext = context;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this );
}
/**
* 异常捕获
*/
@Override
public void uncaughtException (Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null ) {
mDefaultHandler.uncaughtException(thread, ex);
} else {
Intent intent = new Intent(mContext, CrashDialog.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
System.exit(0 );
}
}
/**
* 自定义错误捕获
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException (Throwable ex) {
if (ex == null ) {
return false ;
}
getCrashInfo(ex);
return true ;
}
/**
* 收集错误信息
*
* @param ex
*/
private void getCrashInfo (Throwable ex) {
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 errorMessage = writer.toString();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String mFilePath = Environment.getExternalStorageDirectory() + "/" + App.ERROR_FILENAME;
FileTxt.WirteTxt(mFilePath, FileTxt.ReadTxt(mFilePath) + '\n' + errorMessage);
} else {
Log.i(App.TAG, "哦豁,说好的SD呢..." );
}
}
}
CrashDialog.java
package cn.qson.androidcrash;
/**
* @author x024
*/
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class CrashDialog extends Activity {
private String mFilePath;
private Button btnExit, btnRestart;
private Boolean StorageState = false ;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_crash);
CrashDialog.this .setFinishOnTouchOutside(false );
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
mFilePath = Environment.getExternalStorageDirectory() + "/" + App.ERROR_FILENAME;
StorageState = true ;
} else {
Log.i(App.TAG, "哦豁,说好的SD呢..." );
}
new Thread(upLog).start();
initView();
}
private void initView () {
btnExit = (Button) findViewById(R.id.cash_exit);
btnRestart = (Button) findViewById(R.id.cash_restart);
btnExit.setOnClickListener(mOnClick);
btnRestart.setOnClickListener(mOnClick);
}
OnClickListener mOnClick = new OnClickListener() {
@Override
public void onClick (View v) {
switch (v.getId()) {
case R.id.cash_exit:
exit();
break ;
case R.id.cash_restart:
restart();
break ;
default :
break ;
}
}
};
Runnable upLog = new Runnable() {
@Override
public void run () {
try {
String Mobile = Build.MODEL;
String maxMemory = "" + getmem_TOLAL() / 1024 + "m" ;
String nowMemory = "" + getmem_UNUSED(CrashDialog.this ) / 1024 + "m" ;
String eMessage = "未获取到错误信息" ;
if (StorageState) {
eMessage = FileTxt.ReadTxt(mFilePath).replace("'" , "" );
}
Log.i(App.TAG, "Mobile:" + Mobile + " | maxMemory:" + maxMemory + " |nowMemory:" + nowMemory
+ " |eMessage:" + eMessage);
/**
* 可以在这调你自己的接口上传信息
*/
} catch (Exception e) {
e.printStackTrace();
}
}
};
private void exit () {
FileTxt.deleteFile(mFilePath);
System.exit(0 );
android.os.Process.killProcess(android.os.Process.myPid());
}
private void restart () {
Intent intent = getBaseContext().getPackageManager()
.getLaunchIntentForPackage(getBaseContext().getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
exit();
}
@Override
public void onBackPressed () {
super .onBackPressed();
exit();
}
public static long getmem_UNUSED (Context mContext) {
long MEM_UNUSED;
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
MEM_UNUSED = mi.availMem / 1024 ;
return MEM_UNUSED;
}
public static long getmem_TOLAL () {
long mTotal;
String path = "/proc/meminfo" ;
String content = null ;
BufferedReader br = null ;
try {
br = new BufferedReader(new FileReader(path), 8 );
String line;
if ((line = br.readLine()) != null ) {
content = line;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null ) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
int begin = content.indexOf(':' );
int end = content.indexOf('k' );
content = content.substring(begin + 1 , end).trim();
mTotal = Integer.parseInt(content);
return mTotal;
}
}