在实际开发过程中,我们的APP由于各种原因,难免会有Crash现象(应用程序XXX已经停止)。这样给用户一种很不友好的感觉,那么我们如何去处理这种情况呢?答案就在实现UncaughtchExceptionHanlder,复写uncaughtException()方法。
当crash发生的时候,系统会调用UncaughtchExceptionHanlder#uncaughtException()。在uncaughtException()中我们可以选择收集错误信息,然后保存在SD卡中,在合适的时机将错误日志上传至服务器。这样开发人员在后期维护的时候,就可以有针对性的修复BUG。由于默认的异常处理器是Thread类的静态成员,所以它的作用对象是当前进程的所有线程。下面是一个常用的标准的异常处理器三步走。
一)实现自定义CrashHandler
package com.example.crashhandler;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.os.Process;
import android.util.Log;
import android.widget.Toast;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest.HttpMethod;
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private UncaughtExceptionHandler mDefaultHandler;
private static CrashHandler crashHandler = new CrashHandler();
private Context mContext;
/** 错误日志文件 */
private File logFile = new File(Environment.getExternalStorageDirectory(),"crashLog.trace");
private CrashHandler() {
}
public static CrashHandler getInstance() {
if (crashHandler == null) {
synchronized (CrashHandler.class) {
if (crashHandler == null) {
crashHandler = new CrashHandler();
}
}
}
return crashHandler;
}
public void init(Context context) {
mContext = context;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置为线程默认的异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// 打印异常信息
ex.printStackTrace();
// 我们没有处理异常 并且默认异常处理不为空 则交给系统处理
if (!handlelException(ex) && mDefaultHandler != null) {
// 系统处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// 上传错误日志到服务器
upLoadErrorFileToServer(logFile);
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent(mContext, SplashActivity.class);
// 新开任务栈
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
// 杀死我们的进程
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Process.killProcess(Process.myPid());
}
}, 2 * 1000);
}
}
private boolean handlelException(Throwable ex) {
if (ex == null) {
return false;
}
// 使用Toast来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "程序发生异常,即将重启", Toast.LENGTH_LONG)
.show();
Looper.loop();
}
}.start();
PrintWriter pw = null;
try {
if (!logFile.exists()) {
logFile.createNewFile();
}
pw = new PrintWriter(logFile);
// 收集手机及错误信息
logFile = collectInfoToSDCard(pw, ex);
pw.close();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* 上传错误日志到服务器
*
* @param logFile
* @throws IOException
*/
private void upLoadErrorFileToServer(File errorFile) {
}
/**
* 收集手机信息
*
* @throws NameNotFoundException
*/
private File collectInfoToSDCard(PrintWriter pw, Throwable ex)
throws NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
// 错误发生时间
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
pw.print("time : ");
pw.println(time);
// 版本信息
pw.print("versionCode : ");
pw.println(pi.versionCode);
// 应用版本号
pw.print("versionName : ");
pw.println(pi.versionName);
try {
/** 暴力反射获取数据 */
Field[] Fields = Build.class.getDeclaredFields();
for (Field field : Fields) {
field.setAccessible(true);
pw.print(field.getName() + " : ");
pw.println(field.get(null).toString());
}
} catch (Exception e) {
Log.i(TAG, "an error occured when collect crash info" + e);
}
// 打印堆栈信息
ex.printStackTrace(pw);
return logFile;
}
}
2)在MyApplication中实例化CrashHanlder
package com.example.crashhandler;
import android.app.Application;
public class MyApplication extends Application {
private static MyApplication mInstance;
public static MyApplication getInstance(){
return mInstance;
}
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
}
3)应用到Manifest.xml中
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.crashhandler"
android:versionCode="1"
android:versionName="1.0.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".SplashActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" >
</activity>
</application>
</manifest>
至此,我们的自定义异常处理器就算写完了。注释比较多,就不赘述了。有不清楚的读者请下面评论,我会一一回复。下面,测试下我们的异常处理器。代码很简单,SplshActivity---->MainActivity。在MainActivity中点击按钮抛出一个空指针异常。详情看代码
package com.example.crashhandler;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
public class SplashActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
MyApplication.getInstance();
// 两秒后进入MainActivity
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
}
}, 2*1000);
}
}
package com.example.crashhandler;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity{
private Button crash;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
crash = (Button) findViewById(R.id.crash);
crash.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
throw new NullPointerException();
}
});
}
}
点击按钮之后,触发我们的全局未捕获异常处理器。手机错误信息保存至SD卡,随后新开任务栈重启SplashActivity。点我下载Demo源代码
待完善:
- 对SD卡是否存在进行判断
- 对错误日志文件轮询,新开service上传至服务器