一个Android应用很难做到完全不会出现crash。当应用发生crash时,程序通常会无法继续运行,但如果能知道发生crash的原因,那么就可以修复问题。可是很多时候当产品发布后,用户在使用过程中发生了crash时,很难获取到crash的信息,使得问题代码没法被定位,这非常不利于产品的持续发展。
我们可以通过实现UncaughtExceptionHandler这个接口来捕捉应用的crash信息。当应用发生crash时,就会调用它的uncaughtException方法,我们自己来实现这个方法,就可以在其中对crash信息进行处理,比如我们可以先把它存储到SD卡中,然后找一个合适的时机上传到服务器上。
下面是一个典型的demo,它实现了UncaughtExceptionHandler:
public class CrashHandler implements Thread.UncaughtExceptionHandler{
private static CrashHandler mInstance = new CrashHandler();
private static final String PATH = Environment.getExternalStorageDirectory() + "/crash/log/";
private Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
private Context mContext;
private CrashHandler(){}
public static CrashHandler getInstance(){
return mInstance;
}
public void init(Context context){
mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mContext = context.getApplicationContext();
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
//将crash信息存储到SD卡中
writeExceptionToSDCard(ex);
//TODO 将crash信息上传到服务器中
} catch (IOException e) {
e.printStackTrace();
}
ex.printStackTrace();
//如果系统有默认的异常处理器,就由系统去结束程序,否则自行结束
if(mDefaultExceptionHandler != null){
mDefaultExceptionHandler.uncaughtException(thread, ex);
}else{
System.exit(0);
}
}
private void writeExceptionToSDCard(Throwable ex) throws IOException{
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
return;
}
File dir = new File(PATH);
if(!dir.exists()){
dir.mkdirs();
}
String currTime = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
File file = new File(dir, currTime + ".txt");
try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
pw.println(currTime);
pw.println();
//写入手机相关的信息
savePhoneInfo(pw);
pw.println();
ex.printStackTrace(pw);
pw.close();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
private void savePhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.println("App Version: " + pi.versionName + "_" + pi.versionCode);
pw.println("Android OS Version: " + Build.VERSION.RELEASE + "_" + Build.VERSION.SDK_INT);
pw.println("Vendor: " + Build.MANUFACTURER);
pw.println("Model: " + Build.MODEL);
pw.println("CPU ABI: " + Build.CPU_ABI);
}
}
需要注意的是,代码中被catch的异常不会交给UncaughtExceptionHandler去处理,它只能收到未被捕获的异常。
下面来模拟一下手动抛出一个异常的情形:
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CrashHandler.getInstance().init(this);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
throw new RuntimeException("SELF TEST EXCEPTION");
}
});
}
}
注意必须先要把这个CrashHandler设为默认的异常处理器,再抛出异常。
点击按钮后,CrashHandler捕获了抛出的RuntimeException(“SELF TEST EXCEPTION”),并把它写入到了SD卡中,存储下来的crash信息如下图所示: