作者:速度coinmer | 来源:互联网 | 2023-08-17 07:17
先建立一个crash项目,项目结构如图:在MainActivity.java代码中,代码是这样写的:[java]viewplaincopyprint?packagecom.sc
先建立一个crash项目,项目结构如图:
data-inited="true" data-media-type="image">
在MainActivity.java代码中,代码是这样写的:
[java] view plaincopyprint?
- class="keyword">package com.scott.crash;
-
- class="keyword">import android.app.Activity;
- class="keyword">import android.os.Bundle;
-
- public class="keyword">class MainActivity class="keyword">extends Activity {
-
- class="keyword">private String s;
-
- class="annotation">@Override
- class="keyword">public class="keyword">void onCreate(Bundle savedInstanceState) {
- class="keyword">super.onCreate(savedInstanceState);
- class="alt"> System.out.println(s.equals( class="string">"any string"));
- }
- }
我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:
data-inited="true" data-media-type="image">
遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。
我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的bug的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。
接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。
Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity和Service。本示例中将在自定义加强版的Application中注册未捕获异常处理器。
Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
大家刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:
[java] view plaincopyprint?
- class="keyword">package com.scott.crash;
-
- class="keyword">import java.io.File;
- class="keyword">import java.io.FileOutputStream;
- class="keyword">import java.io.PrintWriter;
- class="keyword">import java.io.StringWriter;
- class="keyword">import java.io.Writer;
- class="keyword">import java.lang.Thread.UncaughtExceptionHandler;
- class="keyword">import java.lang.reflect.Field;
- class="keyword">import java.text.DateFormat;
- class="keyword">import java.text.SimpleDateFormat;
- import java.util.Date;
- class="keyword">import java.util.HashMap;
- import java.util.Map;
-
- class="keyword">import android.content.Context;
- class="keyword">import android.content.pm.PackageInfo;
- class="keyword">import android.content.pm.PackageManager;
- class="keyword">import android.content.pm.PackageManager.NameNotFoundException;
- class="keyword">import android.os.Build;
- class="keyword">import android.os.Environment;
- class="keyword">import android.os.Looper;
- class="keyword">import android.util.Log;
- class="keyword">import android.widget.Toast;
-
-
- class="comment"> * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
-
- class="comment"> * @author user
-
-
- public class="keyword">class CrashHandler class="keyword">implements UncaughtExceptionHandler {
-
- public class="keyword">static class="keyword">final String TAG = "CrashHandler";
-
- class="comment">//系统默认的UncaughtException处理类
- class="keyword">private Thread.UncaughtExceptionHandler mDefaultHandler;
- class="comment">//CrashHandler实例
- class="keyword">private class="keyword">static CrashHandler INSTANCE = class="keyword">new CrashHandler();
- class="comment">//程序的Context对象
- class="keyword">private Context mContext;
- class="comment">//用来存储设备信息和异常信息
- class="keyword">private Map infos = class="keyword">new HashMap();
-
- class="comment">//用于格式化日期,作为日志文件名的一部分
- class="keyword">private DateFormat formatter = class="keyword">new SimpleDateFormat( class="string">"yyyy-MM-dd-HH-mm-ss");
-
- class="comment">/** 保证只有一个CrashHandler实例 */
- class="keyword">private CrashHandler() {
- }
-
- class="comment">/** 获取CrashHandler实例 ,单例模式 */
- class="keyword">public class="keyword">static CrashHandler getInstance() {
- class="keyword">return INSTANCE;
- }
-
- class="comment">/**
- class="comment"> * 初始化
- class="comment"> *
- class="comment"> * @param context
- class="comment"> */
- public class="keyword">void init(Context context) {
- class="alt"> mContext = context;
- class="comment">//获取系统默认的UncaughtException处理器
- class="alt"> mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
- class="comment">//设置该CrashHandler为程序的默认处理器
- class="alt"> Thread.setDefaultUncaughtExceptionHandler( class="keyword">this);
- }
-
-
- class="comment"> * 当UncaughtException发生时会转入该函数来处理
- class="comment"> */
- class="annotation">@Override
- public class="keyword">void uncaughtException(Thread thread, Throwable ex) {
- if (!handleException(ex) && mDefaultHandler != class="keyword">null) {
- class="comment">//如果用户没有处理则让系统默认的异常处理器来处理
- class="alt"> mDefaultHandler.uncaughtException(thread, ex);
- } else {
- class="alt"> class="keyword">try {
- Thread.sleep( class="number">3000);
- class="alt"> } class="keyword">catch (InterruptedException e) {
- Log.e(TAG, class="string">"error : ", e);
- class="alt"> }
- class="comment">//退出程序
- class="alt"> android.os.Process.killProcess(android.os.Process.myPid());
- System.exit( class="number">1);
- class="alt"> }
- }
-
-
- class="comment"> * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
- class="comment"> *
- class="comment"> * @param ex
- class="comment"> * @return true:如果处理了该异常信息;否则返回false.
- class="comment"> */
- private class="keyword">boolean handleException(Throwable ex) {
- if (ex == class="keyword">null) {
- class="keyword">return class="keyword">false;
- class="alt"> }
- class="comment">//使用Toast来显示异常信息
- new Thread() {
- class="annotation">@Override
- class="alt"> class="keyword">public class="keyword">void run() {
- Looper.prepare();
- class="alt"> Toast.makeText(mContext, class="string">"很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
- Looper.loop();
- class="alt"> }
- }.start();
-
- collectDeviceInfo(mContext);
-
- saveCrashInfo2File(ex);
- return class="keyword">true;
- }
-
-
- class="comment"> * 收集设备参数信息
- class="comment"> * @param ctx
- class="comment"> */
- public class="keyword">void collectDeviceInfo(Context ctx) {
- try {
- PackageManager pm = ctx.getPackageManager();
- class="alt"> PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
- class="keyword">if (pi != class="keyword">null) {
- class="alt"> String versionName = pi.versionName == class="keyword">null ? class="string">"null" : pi.versionName;
- String versionCode = pi.versionCode + class="string">"";
- class="alt"> infos.put( class="string">"versionName", versionName);
- infos.put( class="string">"versionCode", versionCode);
- class="alt"> }
- } catch (NameNotFoundException e) {
- class="alt"> Log.e(TAG, class="string">"an error occured when collect package info", e);
- }
- class="alt"> Field[] fields = Build. class="keyword">class.getDeclaredFields();
- class="keyword">for (Field field : fields) {
- class="alt"> class="keyword">try {
- field.setAccessible( class="keyword">true);
- class="alt"> infos.put(field.getName(), field.get( class="keyword">null).toString());
- Log.d(TAG, field.getName() + class="string">" : " + field.get(null));
- class="alt"> } class="keyword">catch (Exception e) {
- Log.e(TAG, class="string">"an error occured when collect crash info", e);
- class="alt"> }
- }
- }
-
- class="comment">/**
- class="comment"> * 保存错误信息到文件中
- class="comment"> *
- class="comment"> * @param ex
- class="comment"> * @return 返回文件名称,便于将文件传送到服务器
- class="comment"> */
- class="keyword">private String saveCrashInfo2File(Throwable ex) {
-
- class="alt"> StringBuffer sb = class="keyword">new StringBuffer();
- class="keyword">for (Map.Entry entry : infos.entrySet()) {
- class="alt"> String key = entry.getKey();
- String value = entry.getValue();
- class="alt"> sb.append(key + class="string">"=" + value + class="string">"\n");
- }
- class="alt">
- Writer writer = class="keyword">new StringWriter();
- class="alt"> PrintWriter printWriter = class="keyword">new PrintWriter(writer);
- ex.printStackTrace(printWriter);
- class="alt"> Throwable cause = ex.getCause();
- class="keyword">while (cause != class="keyword">null) {
- class="alt"> cause.printStackTrace(printWriter);
- cause = cause.getCause();
- class="alt"> }
- printWriter.close();
- class="alt"> String result = writer.toString();
- sb.append(result);
- try {
- class="keyword">long timestamp = System.currentTimeMillis();
- class="alt"> String time = formatter.format( class="keyword">new Date());
- String fileName = class="string">"crash-" + time + "-" + timestamp + class="string">".log";
- class="alt"> class="keyword">if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- String path = class="string">"/sdcard/crash/";
- class="alt"> File dir = class="keyword">new File(path);
- class="keyword">if (!dir.exists()) {
- class="alt"> dir.mkdirs();
- }
- class="alt"> FileOutputStream fos = class="keyword">new FileOutputStream(path + fileName);
- fos.write(sb.toString().getBytes());
- class="alt"> fos.close();
- }
- class="alt"> class="keyword">return fileName;
- } catch (Exception e) {
- class="alt"> Log.e(TAG, class="string">"an error occured while writing file...", e);
- }
- return class="keyword">null;
- }
- }
在收集异常信息时,朋友们也可以使用Properties,因为Properties有一个很便捷的方法properties.store(OutputStream
out, String
comments),用来将Properties实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成Map来存放这些信息,然后生成文件时稍加了些操作。
完成这个CrashHandler后,我们需要在一个Application环境中让其运行,为此,我们继承android.app.Application,添加自己的代码,CrashApplication.java代码如下:
[java] view plaincopyprint?
- class="keyword">package com.scott.crash;
-
- class="keyword">import android.app.Application;
-
- public class="keyword">class CrashApplication class="keyword">extends Application {
- class="annotation">@Override
- class="keyword">public class="keyword">void onCreate() {
- class="keyword">super.onCreate();
- class="alt"> CrashHandler crashHandler = CrashHandler.getInstance();
- crashHandler.init(getApplicationContext());
- }
- }
最后,为了让我们的CrashApplication取代android.app.Application的地位,在我们的代码中生效,我们需要修改AndroidManifest.xml:
[html] view plaincopyprint?
- < class="tag-name">application class="attribute">android:name= class="attribute-value">".CrashApplication" ... class="tag">>
- class="tag-name">application class="tag">>
因为我们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:
[html] view plaincopyprint?
- < class="tag-name">uses-permission class="attribute">android:name= class="attribute-value">"android.permission.WRITE_EXTERNAL_STORAGE" class="tag">/>
搞定了上边的步骤之后,我们来运行一下这个项目:
data-inited="true" data-media-type="image">
看以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息。
然后看一下SDCARD生成的文件:
data-inited="true" data-media-type="image">
用文本编辑器打开日志文件,看一段日志信息:
[java] view plaincopyprint?
- CPU_ABI=armeabi
- CPU_ABI2=unknown
- ID=FRF91
- MANUFACTURER=unknown
- BRAND=generic
- TYPE=eng
- ......
- Caused by: java.lang.NullPointerException
- class="alt"> at com.scott.crash.MainActivity.onCreate(MainActivity.java: class="number">13)
- at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java: class="number">1047)
- class="alt"> at android.app.ActivityThread.performLaunchActivity(ActivityThread.java: class="number">2627)
- ... class="number">11 more
这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照Android中使用HTTP服务相关介绍。
不过在使用HTTP服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:
[java] view plaincopyprint?
-
- class="comment"> * 网络是否可用
- class="comment"> *
- class="comment"> * @param context
- class="comment"> * @return
- class="comment"> */
- class="keyword">public class="keyword">static class="keyword">boolean isNetworkAvailable(Context context) {
- ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- class="alt"> NetworkInfo[] info = mgr.getAllNetworkInfo();
- class="keyword">if (info != class="keyword">null) {
- class="alt"> class="keyword">for ( class="keyword">int i = class="number">0; i < info.length; i++) {
- class="keyword">if (info[i].getState() == NetworkInfo.State.CONNECTED) {
- class="alt"> class="keyword">return class="keyword">true;
- }
- class="alt"> }
- }
- return class="keyword">false;
- }