原因说明:由于我们写的代码难免会出现一些bug,以及由于测试环境和生产环境差异等导致在测试过程中没有发现问题,而在app上线之后会偶然出现的一些bug,如app在使用过程中出现ANR、app卡死、黑屏等现象。
解决思路:我们开发者需要去捕获
这些异常,收集这些异常信息,并且上传到服务器,利于开发人员去解决
这些问题,同时,我们还需要给用户一个友好的交互体验
。
(1) 自定义一个应用异常捕获类 CrashHandler 实现Thread.UncaughtExceptionHandler接口,并重写uncaughtException方法 来 按我们自己的方式处理异常
- 单例模式
获取捕获类实例
-
初始化
捕获类 -
重写
uncaughtException方法处理异常
package comi.example.liy.firstbasicproject.tool;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import static comi.example.liy.firstbasicproject.tool.GlobalConstant.localLogDirectory;
/**
* Created by liy on 2017-06-22.
* 全局异常捕获的机制
* (1)自定义异常收集类CrashHandler并实现UncaughtExceptionHandler接口,并保留系统默认异常处理。
* (2)然后实现uncaughtException方法,当程序发生Uncaught异常的时候,有该方法来接管程序(并可记录或发送错误报告到服务器)
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
public static final String TAG = CrashHandler.class.getName();
private Context mContext;//程序的Context对象
private File logFile = null;//错误记录文件
private Thread.UncaughtExceptionHandler mDefaultHandler;//系统默认的UncaughtException处理类
/**
* 保证只有一个CrashHandler实例
*/
private static CrashHandler instance;//CrashHandler实例
private CrashHandler() {
}
//获取CrashHandler实例:在整个app中全局捕获异常,所以我们自定义的捕获类是单例模式
public static CrashHandler getInstance() {
if (instance == null)
instance = new CrashHandler();
return instance;
}
/**
* 初始化捕获
*/
public void init(Context context) {
mContext = context;
logFile = new File(localLogDirectory);
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();//获取系统默认的UncaughtException处理器
Thread.setDefaultUncaughtExceptionHandler(this);//设置该CrashHandler为程序的默认异常处理器
}
/**
* 当UncaughtException发生时会转入该函数来处理。在线程因未捕获的异常而临近死亡时被调用。
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// 如果我们没处理异常,并且系统默认的异常处理器不为空,则交给系统默认的异常处理器来处理
if (!handleException(ex) && mDefaultHandler != null) {
mDefaultHandler.uncaughtException(thread, ex); // 系统处理
} else {
/*upLoadErrorFileToServer(logFile);//已经记录完log,也可以把这个记录的错误日志发到服务端*/
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, "error : ", e);
}
//捕获异常马上关闭app,即将app整个进程杀死
android.os.Process.killProcess(android.os.Process.myPid());// 退出程序
}
}
/**
* 自定义异常处理:收集错误信息至本地(可选)、发送错误报告到服务器(可以将整个文件上传 或者 上传异常信息的字符串)等
* true代表处理该异常,不再向上抛异常,
* false代表不处理该异常(可以将该log信息存储起来)然后交给上层(这里就到了系统的异常处理)去处理
*/
private boolean handleException(final Throwable ex) {
if (ex == null) {
return false;
}
//提示对话框:使用Toast来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Log.v("exception_info","Exception Info:" + ex + "");
Toast.makeText(mContext,"Exception Info:"+ex.getMessage(), Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
//保存到sd卡:记录异常信息,把错误信息记录到手机卡中,如果异常为空,则把权限交给系统。
PrintWriter pw = null;
try {
File parentDir = new File(logFile.getParent());
if(!parentDir.exists()){
parentDir.mkdirs();
}
if(!logFile.exists()){
try{
logFile.createNewFile();
}catch (Exception e){
Log.v("exception_mkdirs","exception info-->>"+e.getMessage());
}
}
pw = new PrintWriter(logFile);
//收集记录错误信息至本地
collectInfoToSDCard(pw, ex);
pw.close();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* 收集记录错误信息至本地
**/
private void collectInfoToSDCard(PrintWriter pw, Throwable ex) throws PackageManager.NameNotFoundException, IllegalAccessException, IllegalArgumentException {
PackageManager pm = mContext.getPackageManager();
PackageInfo mPackageInfo = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.println("time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); // 记录错误发生的时间
pw.println("versionCode: " + mPackageInfo.versionCode); // 版本号
pw.println("versionName: " + mPackageInfo.versionName); // 版本名称
//Build类,这个类定义了所有关于手机的一些参数,如版本号,系统名称,Android版本等。然后通过反射机制,把这些信息和错误信息一起记录到日志里面。
Field[] fields = Build.class.getDeclaredFields();//获取所有属性
for (Field field : fields) {
field.setAccessible(true);//可以读取private属性并可对其进行更改
pw.print(field.getName() + " : ");
pw.println(field.get(null).toString());
}
ex.printStackTrace(pw);
}
}
保存在sd卡中的异常文件格式(示例):参考
(2) 全局捕获异常:自定义Application,并在AndroidManifest.xml中进行配置
public class APPAplication extends Application {
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
// 捕捉异常:初始化自定义的异常捕获类
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
}
}
(3) activity中捕获异常
- HomeActivity
/**
* Created by liy on 2018-05-30.
*/
public class HomeActivity extends Activity {
private Button btnCrashHandler;
private CrashHandler crashHandler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
initViews();
initData();
initListeners();
}
@Override
public void initViews() {
btnCrashHandler = (Button)findViewById(R.id.activity_main_crashHandler);
}
@Override
public void initData() {
crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
@Override
public void initListeners() {
btnCrashHandler.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Integer a = null;
a.toString();//模拟一个空指针异常,触发nullpointer运行时错误
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
- ActivityGeneralInterface接口
public interface ActivityGeneralInterface {
void initViews();
void initData();
void initListeners();
}
- activity_home.xml