Crash: 处理UncaughtExcption,捕获未处理异常信息,界面友好提示用户
来源:互联网 发布:优衣库网站的seo分析 编辑:程序博客网 时间:2024/05/16 18:33
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; }}
源码下载地址
- Crash: 处理UncaughtExcption,捕获未处理异常信息,界面友好提示用户
- C#捕获未处理异常
- nodejs 捕获未处理异常
- Android_程序未处理异常的捕获与处理
- Android_程序未处理异常的捕获与处理
- Winform 统一捕获异常,捕获未处理异常
- WINFORM未处理异常之捕获
- C# 捕获未处理的异常
- WPF捕获未处理的异常
- AppDomain的两个小技巧(程序运行完毕和未处理异常的处理) 捕获未处理异常
- 用户友好输入界面
- 用户友好界面
- 未处理异常的处理
- android系统异常信息捕获处理
- Python异常信息的捕获和处理
- 捕获asp.net下的未处理异常
- C# WinForm捕获未处理的异常
- 自定义CrashHandler捕获全局未处理异常
- 2015福建省赛 fzoj RunningMan 2221 (数学博弈)
- 启动一个守护进程的唯一副本
- java基础笔记(二--TreeSet)
- UNIX哲学之echo
- fmdb 的简单使用
- Crash: 处理UncaughtExcption,捕获未处理异常信息,界面友好提示用户
- BZOJ1900: Uva10601 Cubes
- 致首次创业者:如果做到了这三点,想不成功都难
- android中的syle和theme
- http session
- 进程间通信(IPC)方式
- C语言的利用
- Android-x86 项目简介
- android-Supporting Different Devices