在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)?在
在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)
?
在 MainActivity.java 代码中,代码是这样写的:
?
[java]?view plaincopy
- package?com.scott.crash;??
- ??
- import?android.app.Activity;??
- import?android.os.Bundle;??
- ??
- public?class?MainActivity?extends?Activity?{??
- ??
- ????private?String?s;??
- ??
- ????@Override??
- ????public?void?onCreate(Bundle?savedInstanceState)?{??
- ????????super.onCreate(savedInstanceState);??
- ????????System.out.println(s.equals("any?string"));??
- ????}??
- ??
- }??
?
我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:
?
遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。
我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的 BUG 的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。
?
接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application 和java.lang.Thread.UncaughtExceptionHandler。
? ??1、Application:用来管理应用程序的全局状态。在应用程序启动时 Application 会首先创建,然后才会根据情况(Intent)来启动相应的Activity 和 Service。本示例中将在自定义加强版的 Application 中注册未捕获异常处理器。
? ??2、Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
?
大家在刚才的项目结构图中看到的 CrashHandler.java 实现了 Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:
?
[java]?view plaincopy
- package?com.scott.crash;????
- ????
- 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?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.os.Looper;????
- import?android.util.Log;????
- import?android.widget.Toast;??
- ??
- /**??
- ?*?UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.??
- ?*???
- ?*?@author?user??
- ?*???
- ?*/??
- public?class?CrashHandler?implements?UncaughtExceptionHandler?{??
- ??
- ????public?static?final?String?TAG?=?"CrashHandler";??
- ??
- ????//?CrashHandler?实例??
- ????private?static?CrashHandler?INSTANCE?=?new?CrashHandler();??
- ??
- ????//?程序的?Context?对象??
- ????private?Context?mContext;??
- ??
- ????//?系统默认的?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;??
- ????}??
- ??
- ????/**?
- ?????*?初始化?
- ?????*?
- ?????*?@param?context?
- ?????*/??
- ????public?void?init(Context?context)?{??
- ????????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?{??
- ????????????try?{??
- ????????????????Thread.sleep(3000);??
- ????????????}?catch?(InterruptedException?e)?{??
- ????????????????Log.e(TAG,?"error?:?",?e);??
- ????????????}??
- ??
- ????????????//?退出程序,注释下面的重启启动程序代码
- ????????????android.os.Process.killProcess(android.os.Process.myPid());??
- ????????????System.exit(1);??
- ? ? ?
- ? ? ?// 重新启动程序,注释上面的退出程序
- ? ? ? ? ? ?Intent intent = new Intent();
- ? ? ? ? ? ?intent.setClass(mContext,MainActivity.class);
- ? ? ? ? ? ?intent.addFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
- ? ? ? ? ? ?mContext.startActivity(intent);
- ? ? ? ? ? ?android.os.Process.killProcess(android.os.Process.myPid());
- ? ? ? ? }??
- ????}??
- ??
- ????/**?
- ?????*?自定义错误处理,收集错误信息,发送错误报告等操作均在此完成?
- ?????*??
- ?????*?@param?ex?
- ?????*?@return?true:如果处理了该异常信息;否则返回?false?
- ?????*/??
- ????private?boolean?handleException(Throwable?ex)?{??
- ????????if?(ex?==?null)?{??
- ????????????return?false;??
- ????????}??
- ??
- ????????//?使用?Toast?来显示异常信息??
- ????????new?Thread()?{??
- ????????????@Override??
- ????????????public?void?run()?{??
- ????????????????Looper.prepare();??
- ????????????????Toast.makeText(mContext,?"很抱歉,程序出现异常,即将退出。",?Toast.LENGTH_LONG).show();??
- ????????????????Looper.loop();??
- ????????????}??
- ????????}.start();??
- ??
- ????????//?收集设备参数信息??
- ????????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());??
- ????????????????Log.d(TAG,?field.getName()?+?"?:?"?+?field.get(null));??
- ????????????}?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?=?"crash-"?+?time?+?"-"?+?timestamp?+?".log";??
- ??????????????
- ????????????if?(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))?{??
- ????????????????String?path?=?"/sdcard/crash/";??
- ????????????????File?dir?=?new?File(path);??
- ????????????????if?(!dir.exists())?{??
- ????????????????????dir.mkdirs();??
- ????????????????}??
- ????????????????FileOutputStream?fos?=?new?FileOutputStream(path?+?fileName);??
- ????????????????fos.write(sb.toString().getBytes());??
- ????????????????fos.close();??
- ????????????}??
- ??
- ????????????return?fileName;??
- ????????}?catch?(Exception?e)?{??
- ????????????Log.e(TAG,?"an?error?occured?while?writing?file...",?e);??
- ????????}??
- ??
- ????????return?null;??
- ????}??
- }??
?
?
在收集异常信息时,朋友们也可以使用 Properties,因为 Properties 有一个很便捷的方法 properties.store(OutputStream out, String comments),用来将Properties 实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成 Map 来存放这些信息,然后生成文件时稍加了些操作。
?
完成这个 CrashHandler 后,我们需要在一个 Application 环境中让其运行,为此,我们继承 android.app.Application,添加自己的代码,CrashApplication.java代码如下:
?
[java]?view plaincopy
- package?com.scott.crash;??
- ??
- import?android.app.Application;??
- ??
- public?class?CrashApplication?extends?Application?{??
- ??????
- ????@Override??
- ????public?void?onCreate()?{??
- ????????super.onCreate();??
- ????????CrashHandler?crashHandler?=?CrashHandler.getInstance();??
- ????????crashHandler.init(getApplicationContext());??
- ????}??
- ??
- }??
?
最后,为了让我们的 CrashApplication 取代 android.app.Application 的地位,在我们的代码中生效,我们需要修改 AndroidManifest.xml:
?
[java]?view plaincopy
- <application?android:name=".CrashApplication"?...>??
- </application>???
?
因为我们上面的 CrashHandler 中,遇到异常后要保存设备参数和具体异常信息到 SDCARD,所以我们需要在 AndroidManifest.xml 中加入读写 SDCARD 权限:
?
[java]?view plaincopy
- <uses-permission?android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>??
?
搞定了上边的步骤之后,我们来运行一下这个项目:
?
可以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息,然后看一下 SDCARD 生成的文件:
?
用文本编辑器打开日志文件,看一段日志信息:
?
[java]?view plaincopy
- CPU_ABI=armeabi??
- CPU_ABI2=unknown??
- ID=FRF91??
- MANUFACTURER=unknown??
- BRAND=generic??
- TYPE=eng??
- ......??
- Caused?by:?java.lang.NullPointerException????
- ????at?com.scott.crash.MainActivity.onCreate(MainActivity.java:13)????
- ????at?android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)????
- ????at?android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)????
- ????...?11?more??
?
?
这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照 Android 中使用 HTTP 服务相关介绍。
不过在使用 HTTP 服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:
?
[java]?view plaincopy
- /**?
- ?*?网络是否可用?
- ?*?
- ?*?@param?context?
- ?*?@return?
- ?*/??
- public?static?boolean?isNetworkAvailable(Context?context)?{??
- ????ConnectivityManager?mgr?=?(ConnectivityManager)?context.getSystemService(Context.CONNECTIVITY_SERVICE);??
- ????NetworkInfo[]?info?=?mgr.getAllNetworkInfo();??
- ????if?(info?!=?null)?{??
- ????????for?(int?i?=?0;?i?<?info.length;?i++)?{??
- ????????????if?(info[i].getState()?==?NetworkInfo.State.CONNECTED)?{??
- ????????????????return?true;??
- ????????????}??
- ????????}??
- ????}??
- ????return?false;??
- }??