Android应用不可避免的发生异常,即系统奔溃,这不是任何人能避免的,无论你的程序写的多么完美,它也不会完全避免异常的发生,其中的原因我们就不去研究了。当异常发生后,系统就会kill掉正在运行的程序,现象就是发生闪退或者提示用户程序无响应和停止运行,这是我们长达程序猿同胞们最不愿意见到的场景。更糟糕的是,当你上线后的应用发生异常,开发者确无法得知程序为何奔溃了,更谈不上去处理它了。幸好,Android提供了处理这类问题的方法,即是Thread类中的一个方法setDefaultUncaughtExceptionHandler
/**
* Set the default handler invoked when a thread abruptly terminates
* due to an uncaught exception, and no other handler has been defined
* for that thread.
*
* <p>Uncaught exception handling is controlled first by the thread, then
* by the thread's {@link ThreadGroup} object and finally by the default
* uncaught exception handler. If the thread does not have an explicit
* uncaught exception handler set, and the thread's thread group
* (including parent thread groups) does not specialize its
* <tt>uncaughtException</tt> method, then the default handler's
* <tt>uncaughtException</tt> method will be invoked.
* <p>By setting the default uncaught exception handler, an application
* can change the way in which uncaught exceptions are handled (such as
* logging to a specific device, or file) for those threads that would
* already accept whatever "default" behavior the system
* provided.
*
* <p>Note that the default uncaught exception handler should not usually
* defer to the thread's <tt>ThreadGroup</tt> object, as that could cause
* infinite recursion.
*
* @param eh the object to use as the default uncaught exception handler.
* If <tt>null</tt> then there is no default handler.
*
* @throws SecurityException if a security manager is present and it
* denies <tt>{@link RuntimePermission}
* ("setDefaultUncaughtExceptionHandler")</tt>
*
* @see #setUncaughtExceptionHandler
* @see #getUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
实现方法:让我们的类继承Thread.UncaughtExceptionHandler,然后重写它里面的uncaughtException方法就可以了
当异常发生的时候,系统就会回调UncaughtExceptionHandler的uncaughtException的方法,在uncaughtException方法中我们就可以获取到异常信息,我们可以选择把异常信息保存在SD卡中,然后在合适的时机上传到网络服务器中,这样开发者就可以分析用户的错误然后在后面的版本中去修复这个bug,当然,我们为了给用户一个良好的体验,可以选择弹出一个对话框甚至跳转一个界面告诉用户程序奔溃了,然后在退出,这样做比闪退的体验好了不止一倍了。
下面就是一个异常处理器的实现类:
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true;
private static final String PATH = Environment.getExternalStorageDirectory()
.getAbsolutePath()
+ File.separator
+ File.separator;
private static final String FILE_NAME = "crashText";
private static final String FILE_NAME_SUFFIX = ".log";
private static CrashHandler sInstance = new CrashHandler();
private Thread.UncaughtExceptionHandler mDefaultCrasHandler;
private Context mContext;
public CrashHandler() {
}
public static CrashHandler getsInstance() {
return sInstance;
}
public void init(Context context) {
mDefaultCrasHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mContext = context.getApplicationContext();
}
/**
* 这是最关键的函数,当程序中有未捕获的异常,系统将会自动调用这个方法,
* thread为出现未捕获异常的线程,ex为为未捕获的异常,有了这个ex,我们就可以得到
* 异常信息了
*
* @param thread
* @param ex
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void uncaughtException(Thread thread, Throwable ex) {
//导出异常信息到SD卡中
try {
dumpExceptionToSDCard(ex);
//上传异常信息到服务器,便于开发人员分析日志从而解决bug
uploadExceptionToServer();
} catch (Exception e1) {
e1.printStackTrace();
}
ex.printStackTrace();
//如果系统提供了默认的异常处理器,则交给系统去结束程序,否则就由自己结束自己
if (mDefaultCrasHandler != null) {
mDefaultCrasHandler.uncaughtException(thread, ex);
} else {
Process.killProcess(Process.myPid());
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void dumpExceptionToSDCard(Throwable ex) throws IOException {
//如果SD卡不存在或无法使用,则无法把异常信息写入SD卡中
if (!Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.i(TAG, "dumpExceptionToSDCard: SD卡不存在");
return;
}
}
File dir = new File(PATH);
if (!dir.exists()) {
dir.mkdirs();
}
long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(current));
File file = new File(PATH + FILE_NAME);
//先删除之前的异常信息
if (file.exists()) {
DeleteFile(file);
}
if (!file.exists()) {
file.mkdirs();
}
StackTraceElement[] stackTrace = ex.getStackTrace();
String error_text = "错误:" + ex.toString() + " \n ";
for (int i = 0; i < stackTrace.length; i++) {
error_text += stackTrace[i].getFileName() + " class:"
+ stackTrace[i].getClassName() + " method:"
+ stackTrace[i].getMethodName() + " line:"
+ stackTrace[i].getLineNumber() + " \n ";
}
try {
PrintWriter pw = new PrintWriter(
new BufferedWriter(
new FileWriter(file + File.separator
+ time + FILE_NAME_SUFFIX)));
pw.println(time);
dumpPhoneInfo(pw);
pw.println();
ex.printStackTrace(pw);
pw.close();
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "dumpExceptionToSDCard: " + e);
}
}
/**
* 写入手机的基本信息
*
* @param pw
*/
private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.print("App Version: ");
pw.print(pi.versionName);
pw.print('_');
pw.println(pi.versionCode);
//Android版本号
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print('_');
pw.println(Build.VERSION.SDK_INT);
//手机制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
//手机型号
pw.print("Model: ");
pw.println(Build.MODEL);
//CUP架构
pw.print("CUP ABI: ");
pw.println(Build.CPU_ABI);
//奔溃发生时间
pw.print("CURRENT DATE: ");
Date currentDate = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
pw.println(dateFormat.format(currentDate));
}
private void uploadExceptionToServer() {
//TODO Upload Exception Message To Your Web Server
}
当应用奔溃的时候,CrashHandler会将异常信息及设备信息保存到SD卡中,接着将异常交给系统去处理,系统会帮助我们中止程序。
那么该如何去使用CrashHandler呢?很简单,我们在Application初始化的时候设置CrashHandler,如下所示:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler crashHandler = CrashHandler.getsInstance();
crashHandler.init(this);
}
}
这样我们就可以很方便的到服务器上去查看用户的奔溃信息了,需要注意的是,代码中被catch的异常是不会交给CrashHandler处理的,CrashHandler只能收到那些没有被捕获的异常,下面我们测试一下,如下所示,我们抛出一个异常。
public class MainActivityextends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int s = 2 / 0;//除数不能为0
}
}
在这里,我们为了不为用户增加内存的负担,当发生奔溃的时候,将以前保存在SD卡中的异常信息删除,这样就不会每次出现奔溃信息后就创建一个文件,节省内存。
/**
* 递归删除文件和文件夹
* @param file 要删除的根目录
*/
public void DeleteFile(File file){
if(file.isFile()){
file.delete();
return;
}
if(file.isDirectory()){
File[] childFile = file.listFiles();
if(childFile == null || childFile.length == 0){
file.delete();
return;
}
for(File f : childFile){
DeleteFile(f);
}
file.delete();
}
}
在文章的开头我提到过,为了给用户好的体验,我决定跳转一个界面来提示用户发生了异常,如图所示这样的界面,用户的体验是不是会上一层楼呢?
我们在uncaughtException方法中调用跳转界面
new Thread() {
@Override
public void run() {
Looper.prepare();
try {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("com.error.text.NOTIFY_ERROR");
mContext.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
Looper.loop();
}
}.start();
然后在AndroidManifest.xml中配置Activity,记住这里是隐式启动Activity,代码如下所示:
<activity
android:name=".ErrorActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="portrait"
>
<intent-filter>
<action android:name="com.error.text.NOTIFY_ERROR" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
界面大致是这样的:
![](https://img.haomeiwen.com/i4740566/b8f904ea4be16895.png)
完全退出帮助类
public class CompleteQuit extends Application {
private Stack<Activity> activityStack;
private static CompleteQuit instance;
private CompleteQuit() {
}
/**
* 单例模式中获取唯一的CompleteQuit实例
*/
public static CompleteQuit getInstance() {
if (null == instance) {
instance = new CompleteQuit();
}
return instance;
}
/**
* 添加Activity到容器中
*/
public void pushActivity(Activity activity) {
if (activityStack == null) {
activityStack = new Stack<Activity>();
}
activityStack.add(activity);
}
/**
* 退出栈中所有Activity(唯一列外)
*/
@SuppressWarnings("rawtypes")
public void exitAllButOne(Class cls) {
while (true) {
Activity activity = currentActivity();
if (activity == null) {
break;
}
if (activity.getClass().equals(cls)) {
break;
}
popActivity(activity);
}
}
/**
* 退出栈中所有Activity
*/
public void exitAll(boolean exit) {
while (true) {
Activity activity = currentActivity();
if (activity == null) {
break;
}
popActivity(activity);
}
if (exit) {
System.exit(0);
}
}
/**
* 获得当前栈顶Activity
*/
public Activity currentActivity() {
Activity activity = null;
if (activityStack != null && !activityStack.empty())
activity = activityStack.lastElement();
return activity;
}
/**
* 退出栈顶Activity
*/
public void popActivity(Activity activity) {
if (activity != null) {
// 在从自定义集合中取出当前Activity时,也进行了Activity的关闭操作
activity.finish();
activityStack.remove(activity);
activity = null;
}
}
}
到此我们的异常事件处理器就完成了,快快为你的应用加上默认的异常事件处理器吧!!!
![](https://img.haomeiwen.com/i4740566/39d18aaa9d6fc019.jpg)
![](https://img.haomeiwen.com/i4740566/6d2d8865c9a8f631.jpg)
附上Github地址
网友评论