Android中处理崩溃异常

你们都知道,如今安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者我的不可能购买全部设备逐个调试,因此在程序发布出去以后,若是出现了崩溃现象,开发者应该及时获取在该设备上致使崩溃的信息,这对于下一个版本的bug修复帮助极大,因此今天就来介绍一下如何在程序崩溃的状况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。html

咱们先创建一个crash项目,项目结构如图:java

图片说明文字

在MainActivity.java代码中,代码是这样写的:
[java] view plaincopyandroid

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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"));  
    }  
}  

咱们在这里故意制造了一个潜在的运行期异常,当咱们运行程序时就会出现如下界面:web

图片说明文字

遇到软件没有捕获的异常以后,系统会弹出这个默认的强制关闭对话框。服务器

咱们固然不但愿用户看到这种现象,简直是对用户心灵上的打击,并且对咱们的bug的修复也是毫无帮助的。咱们须要的是软件有一个全局的异常捕获器,当出现一个咱们没有发现的异常时,捕获这个异常,而且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体缘由。网络

接下来咱们就来实现这一机制,不过首先咱们仍是来了解如下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。并发

Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先建立,而后才会根据状况(Intent)来启动相应的Activity和Service。本示例中将在自定义增强版的Application中注册未捕获异常处理器。app

Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。若是程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。咱们须要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就能够作一些个性化的异常处理操做。编辑器

你们刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使咱们用来处理未捕获异常的主要成员,代码以下:
[java] view plaincopyide

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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";  

    //系统默认的UncaughtException处理类   
    private Thread.UncaughtExceptionHandler mDefaultHandler;  
    //CrashHandler实例  
    private static CrashHandler INSTANCE = new CrashHandler();  
    //程序的Context对象  
    private Context mContext;  
    //用来存储设备信息和异常信息  
    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);  
        }  
    }  

    /** 
     * 自定义错误处理,收集错误信息 发送错误报告等操做均在此完成. 
     *  
     * @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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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:
[html] view plaincopy

1
2
<application android:name=".CrashApplication" ...>  
</application>  

由于咱们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,因此咱们须要在AndroidManifest.xml中加入读写SDCARD权限:
[html] view plaincopy

1
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

搞定了上边的步骤以后,咱们来运行一下这个项目:

图片说明文字

看以看到,并不会有强制关闭的对话框出现了,取而代之的是咱们比较有好的提示信息。

而后看一下SDCARD生成的文件:

图片说明文字

用文本编辑器打开日志文件,看一段日志信息:
[java] view plaincopy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/** 
     * 网络是否可用 
     *  
     * @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;  
    }  

 

转载自:http://my.eoe.cn/817027/archive/17997.html

相关文章
相关标签/搜索