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,首先找到需要LauncherActivitypublic 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; } }
源码下载地址
相关文章推荐
- 构造函数的那些事
- CentOS修改主机名hostname
- Spring之上传文件
- 每日一vim(3)
- windows 环境下 python 安装matplotlib
- 每日一vim(2)简单搜索
- You need to use a Theme.AppCompat theme (or descendant) with this activity.
- java链表操作
- tomcat 配多个service和HOST
- 静态内容生成器——Wyam
- PHP后台技术-后台登录安全问题和显示多条图片数据库设计
- linux命令笔记su sudo df du
- JFinal 部署在 Tomcat 下推荐方法
- 基于OSGi实现的热插拔项目
- 国内外知名IT科技博客
- 国内外知名IT科技博客
- JavaScript之JavaScript 库详解<三>
- JSP+Servlet+JavaBean+Dao模式介绍
- systemctl命令用法
- 面向对象的设计模式(二),Builder模式