Android开发之全局异常捕获完美闪退APP专题

时间:2022-12-18 20:46:53

其实写这边文章之前,一直在考虑要不要标注为原创,因为全局异常捕获的机制,自己也是看了别人的文章学来的,百度全局异常捕获,出来的也都是一模一样的内容,只是博客位置不一样而已。但是最后要是决定标准为原创,因为网上的那些全局异常捕获的文章,虽然交代了如何去处理全局异常捕获,但是却没有完美的处理捕获异常之后该做的事情。既没有完美的退出程序。博主在使用网上提供的全局捕获异常之后,不是ANR就是只退出了报错的那个ACTIVITY,没有达到真正的闪退处理。这让博主非常的苦恼,于是博主研究了一天,终于找到了完美退出APP的方法。下面来和大家一起分享。

一、Application的生命周期

在说如何完美退出APP之前,我们先来讲讲Application的生命周期.

1、onCreate,app启动的主入口,程序启动的时候调用

	@Override
public void onCreate() {
super.onCreate();

}
2、onTerminate()  app停止的时候执行的方法,但并不一定会调用。当虚拟机为别的应用程序腾出更大资源空间而终止当前应用程序的时候,是不会执行该方法的。

	public void onTerminate() {
super.onTerminate();
System.exit(0);
}

3、onLowMemory()当后台程序已经终止资源还匮乏的时候,会调用这个方法,一般的程序会在这里释放一些不必要的资源

	@Override
public void onLowMemory() {
super.onLowMemory();
}

4、onConfigurationChanged(Configuration newConfig),当配置改变时调用的方法

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
看完application的生命周期之后,想必大腿对于APP的启动和退出有了一个大概的认识。我们常说activity,fragment,service,BroadcastReceiver的生命周期,但却很少去了解application的生命周期。其实google设计的这个application类有很大的用处。一般来说,application代表整个应用程序,所以它就是一个全局变量,所有的公共变量、需要全局传递的数值都可以通过application来达到目的。而在activiiy中或者其他地方,我们都可以通过getApplication()或者mContext.getApplicationContext()来拿到application的实例。在这里不在描述过多,网上百度可以找到很多的相关信息。好了,下面进入正题。


二、全局异常的捕获及完美闪退出应用程序

直接上代码,代码大部分和网站别人的全局异常捕获一样,但是,在初始化的时候,添加了application参数,在异常处理的方法也和别人的不一样,详细大家请仔细查看注释

package com.batways.tnt.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.batways.tnt.AppActivityManager;
import com.batways.tnt.TntApplication;
import com.batways.tnt.activity.MainActivity;
import com.batways.tnt.activity.ProductActivity;

import android.content.Context;
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.util.Log;
import android.widget.Toast;

/**
* @ClassName: CrashHandler
* @author victor_freedom (x_freedom_reddevil@126.com)
* @createddate 2014-12-25 下午11:41:12
* @Description: UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
*/
public class CrashHandler implements UncaughtExceptionHandler {

public static final String TAG = "CrashHandler";

// CrashHandler 实例
private static CrashHandler INSTANCE = new CrashHandler();

// 程序的 Context 对象
private Context mContext;
<span> </span>// app对象
private TntApplication app;

// 系统默认的 UncaughtException 处理类
private Thread.UncaughtExceptionHandler mDefaultHandler;

// 用来存储设备信息和异常信息
private Map<String, String> infos = new HashMap<String, String>();

// 用于格式化日期,作为日志文件名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

/** 保证只有一个 CrashHandler 实例 */
private CrashHandler() {
}

/** 获取 CrashHandler 实例 ,单例模式 */
public static CrashHandler getInstance() {
return INSTANCE;
}

/**
* @Title: init
* @Description: 初始化
* @param context
* @param app
* 传入的app
* @throws
*/
public void init(Context context, TntApplication app) {
// 传入app对象,为完美终止app
this.app = app;
mContext = context;
// 获取系统默认的 UncaughtException 处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置该 CrashHandler 为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
* 当 UncaughtException 发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
// 释放资源不能像常规的那样在activity的onDestroy方法里面执行,因为如果出现全局异常捕获,activity的关闭有时候是不会再走相关的生命周期函数的(onDesktroy,onStop,onPause等)。
// 这里是博主在退出app之前需要释放掉的一些资源,通过之前讲的AppActivityManager来拿到对应的实例activity释放里面的资源,然后调用AppExit退出应用程序
ProductActivity activitys = (ProductActivity) AppActivityManager
.getAppActivityManager().getActivity(ProductActivity.class);
activitys.offline();
MainActivity activity = (MainActivity) AppActivityManager
.getAppActivityManager().getActivity(MainActivity.class);
activity.offline();
// 当执行这一句的时候,其实APP有时候并没有完美的退出(方法详情可以查看博主之前的写的activity管理的文章)
// 博主的项目里面有网络连接、有后台服务、多线程等各种。执行完这个方法之后,虽然能够闪退出去,但是,当再次进入APP的时候,是回出现ANR的,说明,这样还是没有的完美退出APP
AppActivityManager.getAppActivityManager().AppExit(mContext);
// 之前说application的时候说过,当app退出的时候,会执行onTerminate方法,但是有时候不会主动执行。那么,博主想,如果我们强制执行这个方法,能不能让app完美的终止呢?答案是肯定的。
app.onTerminate();
}
}

/**
* 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
*
* @param ex
* @return true:如果处理了该异常信息;否则返回 false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
// 收集设备参数信息
collectDeviceInfo(mContext);
// 保存日志文件
saveCrashInfo2File(ex);
return true;
}

/**
* 收集设备参数信息
*
* @param ctx
*/
public void collectDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
PackageManager.GET_ACTIVITIES);

if (pi != null) {
String versionName = pi.versionName == null ? "null"
: pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("versionName", versionName);
infos.put("versionCode", versionCode);
}
} catch (NameNotFoundException e) {
Log.e(TAG, "an error occured when collect package info", e);
}
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
} catch (Exception e) {
Log.e(TAG, "an error occured when collect crash info", e);
}
}
}

/**
* 保存错误信息到文件中 *
*
* @param ex
* @return 返回文件名称,便于将文件传送到服务器
*/
private String saveCrashInfo2File(Throwable ex) {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}

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 result = writer.toString();
sb.append(result);
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "error-" + time + "-" + timestamp + ".log";
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/crashs";
Toast.makeText(mContext, path, Toast.LENGTH_LONG).show();
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
fos.write(sb.toString().getBytes());
fos.flush();
fos.close();
}
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
}

return null;
}

}

要在发现全局异常的时候捕获到,只需要在application类中的onCreate方法执行以下代码即可:

CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext(), this);

在application类中,要复写onTerminate方法:

	@Override
public void onTerminate() {
super.onTerminate();
System.exit(0);
}

从此,就可以完美无残留的做到退出APP了。