您的位置:首页 > 其它

Crash: 处理UncaughtExcption,捕获未处理异常信息,界面友好提示用户

2015-12-27 21:32 477 查看

Crash: 处理UncaughtExcption,友好提示用户,捕获错误信息

相信大家在APP使用过程都遇到过,应用程序异常崩溃,屏幕一黑闪退,这种情况称之为Crash。出现的原因是由于程序运行过程中产生了未知异常UncaughtException,当程序发生Crash时,系统会杀死程序,出现闪退,这种情况的用户体验不好,而且开发人员也不能知道用户发生了何种异常。

那么问题来了,发生Crash时我们需要给用户提供一个友好的提醒,并且捕获用户的错误信息。



捕获用户Crash信息的两种思路

1.集成第三方测试SDK,

做的比较好的有 云测TestIn

腾讯云测等等,这里只要看下文档基本10行以内代码就能搞定了,使用简单,但也有局限性。

2.通过自定义实现,通过UncaughtExceptionHandler处理未捕获异常。(下面着重说说这种方式)

具体思路:首先为程序指定UncaughtExceptionHandler处理程序,当未捕获异常发生时,会调用处理程序的uncaughtException方法,我们在该方法中获得相应异常信息,然后做出相应处理 弹出友好的提示框,保存异常信息(或上传到服务器)

UncaughtExceptionHandler文档是这么介绍的

java.lang

接口 Thread.UncaughtExceptionHandler

所有已知实现类:

ThreadGroup

正在封闭类:Thread

public static interface Thread.UncaughtExceptionHandler

当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。

当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler() 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,将线程和异常作为参数传递。如果某一线程没有明确设置其 UncaughtExceptionHandler,则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。

通过指定一个Thread.setDefaultUncaughtExceptionHandler()方法指定未知异常处理程序

//设置该线程由于未捕获到异常而突然终止时调用的处理程序
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
//处理异常,我们还可以把异常信息写入文件,以供后来分析。
String stackTraceString = getThrowableStraceStr(thread, throwable);
Log.e(TAG,stackTraceString);

startErroActivity(stackTraceString);
killCurrentProcess();
}
});


下面 说一下具体的几个方法

获取异常及异常的追踪信息,异常信息和追踪信息如下图



/**
* 获取此 throwable 及其追踪信息
* @param thread
* @param throwable
* @return
*/
private static String getThrowableStraceStr(Thread thread, Throwable throwable) {
Log.e("CrashException", thread.getName() + "--Exception:" + throwable + "\n\n");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);//
return sw.toString();
}


当捕获到异常追踪信息后,我们的处理方式时打开一个专门用于的错误信息Activity用于提示用户,在该并可以用户选择是否重新启动应用程序。

这里有三个地方需要注意的是:

1.将错误信息通过Intent.putExtra(erroInfo)传递给错误提示页面需要保证文本长度不超过128K

2.打开新的ErroActivity是在一个单独的进程中,所以调用杀死当前进程不会杀掉ErroActivity所在进程

3.设置ErroActivity的意图,清空回退栈中所有activity intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)

private static void startErroActivity(String straceMsg) {
//Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
//The limit is 1MB on Android but some devices seem to have it lower.
//See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171 if (straceMsg.length() > MAX_STACK_TRACE_SIZE) {
String disclaimer = " [stack trace too large]";
straceMsg = straceMsg.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;
}

//启动错误页面
Intent intent=new Intent(application,ErroActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(EXTRA_STRACE_MESSAGE,straceMsg);
application.startActivity(intent);
}


在开启错误信息页面后,结束当前进程

/**
* 结束进程
* INTERNAL method that kills the current process. It is used after
* restarting or killing the app.
*/
private static void killCurrentProcess() {
//杀死当前进程
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}


捕获异常完整代码

package com.crash.k.crashuncaughtexception.utils;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.widget.Toast;

import com.crash.k.crashuncaughtexception.ErroActivity;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
* Created by K on 2015/12/21.
*/
public class CrashUncaugtExceptionUtils {

public final static String EXTRA_STRACE_MESSAGE="extra_strace_message";
private static final String TAG="CrashException";

/**设置传递错误堆栈信息最大长度 保证不超过128 KB*/
private static final int MAX_STACK_TRACE_SIZE = 131071; //128 KB - 1

private static Context mContext;
private static Application application;

/**
* 初始化
*/
public static void install(Context context){

//设置该线程由于未捕获到异常而突然终止时调用的处理程序 Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { //处理异常,我们还可以把异常信息写入文件,以供后来分析。 String stackTraceString = getThrowableStraceStr(thread, throwable); Log.e(TAG,stackTraceString); startErroActivity(stackTraceString); killCurrentProcess(); } });

mContext=context;
application=(Application)context.getApplicationContext();
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
Log.i(TAG, "onActivityCreated");

}

@Override
public void onActivityStarted(Activity activity) {

}

@Override
public void onActivityResumed(Activity activity) {
Thread.UncaughtExceptionHandler unCaughtExceptionHandler= Thread.getDefaultUncaughtExceptionHandler();
Log.i("LifecycleCallbacks",activity.getClass().getName()+"-->\n"+ unCaughtExceptionHandler.getClass().getName() + "");
}

@Override
public void onActivityPaused(Activity activity) {

}

@Override
public void onActivityStopped(Activity activity) {

}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

}

@Override
public void onActivityDestroyed(Activity activity) {

}
});
}

private static void startErroActivity(String straceMsg) { //Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent. //The limit is 1MB on Android but some devices seem to have it lower. //See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171 if (straceMsg.length() > MAX_STACK_TRACE_SIZE) { String disclaimer = " [stack trace too large]"; straceMsg = straceMsg.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer; } //启动错误页面 Intent intent=new Intent(application,ErroActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra(EXTRA_STRACE_MESSAGE,straceMsg); application.startActivity(intent); }
/** * 获取此 throwable 及其追踪信息 * @param thread * @param throwable * @return */ private static String getThrowableStraceStr(Thread thread, Throwable throwable) { Log.e("CrashException", thread.getName() + "--Exception:" + throwable + "\n\n"); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw);// return sw.toString(); }

/**
* 获取应用的启动页Activity
* INTERNAL method used to get the default launcher activity for the app.
* If there is no launchable activity, this returns null.
*
* @param context A valid context. Must not be null.
* @return A valid activity class, or null if no suitable one is found
*/
@SuppressWarnings("unchecked")
public static Class<? extends Activity> getLauncherActivity(Context context) { Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); if (intent != null) { try { return (Class<? extends Activity>) Class.forName(intent.getComponent().getClassName()); } catch (ClassNotFoundException e) { //Should not happen, print it to the log! Log.e(TAG, "Failed when resolving the restart activity class via getLaunchIntentForPackage, stack trace follows!", e); } } return null; }
/** * 结束进程 * INTERNAL method that kills the current process. It is used after * restarting or killing the app. */ private static void killCurrentProcess() { //杀死当前进程 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(10); }
}


在Application中完成初始化

封装好了异常处理,在Application中一行代码即可完成初始化了

public class CrashApp extends Application{
@Override
public void onCreate() {
super.onCreate();

//初始化
CrashUncaugtExceptionUtils.install(this);
}
}


ErroActivity中的异常处理

1.重新启动APP,首先找到需要LauncherActivity

public static Class<? extends Activity> getLauncherActivity(Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if (intent != null) {
try {
return (Class<? extends Activity>) Class.forName(intent.getComponent().getClassName());
} catch (ClassNotFoundException e) {
//Should not happen, print it to the log!
Log.e(TAG, "Failed when resolving the restart activity class via getLaunchIntentForPackage, stack trace follows!", e);
}
}
return null;
}


2 .显示异常信息,具体处理可以保存到本地文件统一上传,也可以每次发生异常则上传,具体操作酌情处理

以下错误信息的代码

public class ErroActivity extends AppCompatActivity {

private Context mContext;
private String mErroDetailsStr;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_erro);
initData();
initViews();
}

private void initData(){
mContext=this;
String erroInfo= getIntent().getStringExtra(CrashUncaugtExceptionUtils.EXTRA_STRACE_MESSAGE);
mErroDetailsStr=getErroDetailsInfo()+"\n strace \n"+erroInfo;
}

private void initViews()
{
Button btnRestart=(Button)findViewById(R.id.btn_restart);
btnRestart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Class launcherActivity= CrashUncaugtExceptionUtils.getLauncherActivity(mContext);

if(launcherActivity!=null)
{
Intent intent=new Intent(mContext,launcherActivity);
startActivity(intent);
finish();
}else{
Toast.makeText(mContext,"重启失败了",Toast.LENGTH_SHORT).show();
}
}
});

TextView tvInfo=(TextView)findViewById(R.id.tv_erro_info);
tvInfo.setText(mErroDetailsStr);
}

/**获取错误信息
*
* @param thread
* @param throwable
* @return
*/
private String getErroDetailsInfo()
{
String erroDetailInfo="";
//设备名称
erroDetailInfo+="DeviceName:"+ AppInfoUtils.getDeviceModelName()+ " \n";
//版本号
erroDetailInfo+="VersionName:"+AppInfoUtils.getVersionName(mContext)+ " \n";

DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
//记录当前出错时间
erroDetailInfo+="currentTime"+dateFormat.format(new Date())+ " \n";

//安装时间
erroDetailInfo+="BuildTime:"+AppInfoUtils.getBuildDateAsString(mContext,dateFormat)+ " \n";

return  erroDetailInfo;
}
}


获取设备号,版本号,安装时间的几个方法

package com.crash.k.crashuncaughtexception.utils;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Build;

import java.text.DateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
* Created by K on 2015/12/27.
*/
public class AppInfoUtils {

/**
* 获取应用版本名称.
*
*/
public static String getVersionName(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionName;
} catch (Exception e) {
return "Unknown";
}
}

/**
* 获取设备名称
*/
public static String getDeviceModelName() {
String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL;
if (model.startsWith(manufacturer)) {
return capitalize(model);
} else {
return capitalize(manufacturer) + " " + model;
}
}

/**
* 获取应用安装时间
* @param context 当前上下文,必填
* @param dateFormat 时间格式化
* @return
*/
public static String getBuildDateAsString(Context context, DateFormat dateFormat) {
String buildDate;
try {
ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
ZipFile zf = new ZipFile(ai.sourceDir);
ZipEntry ze = zf.getEntry("classes.dex");
long time = ze.getTime();
buildDate = dateFormat.format(new Date(time));
zf.close();
} catch (Exception e) {
buildDate = "Unknown";
}
return buildDate;
}

/***
* 如果字符串为null则默认转为 “”
* @param s
* @return
*/
private static String capitalize(String s) {
if (s == null || s.length() == 0) {
return "";
}
return  s;
}

}


源码下载地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: