大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。
我们先建立一个crash项目,项目结构如图:
在MainActivity.java代码中,代码是这样写的:
- 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。
Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity和Service。本示例中将在自定义加强版的Application中注册未捕获异常处理器。
Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
大家刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:
- 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;
-
-
-
-
-
-
-
- public class CrashHandler implements UncaughtExceptionHandler {
-
- public static final String TAG = "CrashHandler";
-
-
- private Thread.UncaughtExceptionHandler mDefaultHandler;
-
- private static CrashHandler INSTANCE = new CrashHandler();
-
- private Context mContext;
-
- private Map<String, String> infos = new HashMap<String, String>();
-
-
- private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
-
-
- private CrashHandler() {
- }
-
-
- public static CrashHandler getInstance() {
- return INSTANCE;
- }
-
-
-
-
-
-
- public void init(Context context) {
- mContext = context;
-
- mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
-
- Thread.setDefaultUncaughtExceptionHandler(this);
- }
-
-
-
-
- @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);
- }
- }
-
-
-
-
-
-
-
- private boolean handleException(Throwable ex) {
- if (ex == null) {
- return false;
- }
-
- new Thread() {
- @Override
- public void run() {
- Looper.prepare();
- Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
- Looper.loop();
- }
- }.start();
-
- collectDeviceInfo(mContext);
-
- saveCrashInfo2File(ex);
- return true;
- }
-
-
-
-
-
- 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);
- }
- }
- }
-
-
-
-
-
-
-
- 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代码如下:
- 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:
- <application android:name=".CrashApplication" ...>
- </application>
因为我们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
搞定了上边的步骤之后,我们来运行一下这个项目:
看以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息。
然后看一下SDCARD生成的文件:
用文本编辑器打开日志文件,看一段日志信息:
- 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服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:
-
-
-
-
-
-
- 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;
- }
分享到:
相关推荐
Android 全局异常错误或崩溃捕捉。当出现崩溃,软件不会闪退,会出现弹出一个对话框,异常错误信息保存在sd卡一个文件夹下。后续需要还可以发送到服务器的。我博客网址: http://blog.csdn.net/qq_16064871。我写了...
android前置竖屏摄像头录制视频保存到本地,Camera+MediaRecorder实现,并支持暂停和恢复录制功能,修复快速按暂停和恢复会闪退的异常
AppManager 如何优雅的退出应用和处理崩溃异常自动重启
AndroidCrash全局崩溃异常捕获,友好的提示页面,收集手机信息+捕获异常信息,并上传到服务器,带重启APP功能。
使用android:process和Process.killProcess和UncaughtExceptionHandler完成Android应用程序不异常退出
在android libusbcamear module中,默认JNI库存在bug,在预览过程中拔下usb摄像头,会出现signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 的空指针异常,此so已修改源码重新打包,替换可修复该异常
开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 ...
我最近碰到一个在无网络或网络连接不好的情况下有时候会闪退,后来上网发现了这个Thead类来处理未被捕获的异常.zip,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
移动开发中不可避免的闪退、崩溃等异常一直是困扰开发者难题,该案例是一个全局异常捕获类,方便开发者查看原因,并且将异常信息记录保存到手机本地log日志中,方便在本机测试不出异常却在客户那边出现异常时,客户...
普通的程序如果出现问题了都会强制退出,像360助手之类的程序崩溃的时候还会给出一个是否上传错误到服务器的提示,本项目就是一个简单的全局异常捕捉例子,捕捉到异常以后可以把异常信息写入文件以供后来分析或者用...
在Android手机上显示闪退崩溃信息 解决软件闪退崩溃,但是又不能场景重现的无奈 避免部分国产Rom禁止异常log输出 引入依赖 从1.1.4版本开始全面迁移到androidx,如不想迁移到androidx请继续使用1.1.3版本。 ...
本文实例讲述了Android编程实现捕获程序异常退出时的错误log信息功能。分享给大家供大家参考,具体如下: 很多时候我们程序无缘无故的就挂掉了,让我们一头雾水,如果刚好我们在调试,那我们可以通过错误log来查看是...
它面向android应用程序开发人员,以帮助他们在崩溃或行为异常时从其应用程序获取数据。 截至2020年6月,在Google Play上所有应用程序中使用ACRA的比例为1.57%( )。包括ACRA在内,超过1.3万个应用程序和50亿次...
Android 应用不可避免的会发生crash 即崩溃,无论程序写的多好...java的Thread中有一个UncaughtExceptionHandler接口,该接口的作用主要是为 了 当 Thread 因未捕获的异常而突然终止时,调用处理程序。 崩溃信息捕获,
在项目中,我们的应用经常会遇到崩溃的情况,如果你的项目已经发送到了应用市场上,那么应用发生的崩溃开发人员是开不到的,所以我们要想办法将异常信息传到服务器上,便于开发人员查看并作出修改。Google考虑到这...
1、对软件本身进行100多出优化修改,极大的增加了软件的稳定性,减少了异常情况下的崩溃闪退的问题。 2、优化了在浏览信息源及同步发送时的流量问题,减少了近50%的流量。 3、调整了我的微博等界面文字界面...
支持自动将App异常崩溃报错信息保存到指定文件,方便跟踪处理 特点 作为一款日志管理工具它最大的特点就是简单实用,与Android原生的日志功能相比较它具有以及几个优势: 支持把日志输出到指定文本文件 支持捕获App...
在Android手机上自动显示闪退崩溃信息,直接分享给相关开发人员! 再也不用担心测试妹妹给你重现怎样操作才能触发闪退崩溃的尴尬! 再也不用担心产品给你说哪儿哪儿会闪退崩溃,但是又不能场景还原的无奈! 再也不用...
我们知道出现Crash应用闪退和崩溃一般有三个原因:ANR(程序无响应)、Exception(异常)、LMK(低内存杀死机制)。本文重点介绍LMK机制。 目的:了解LMK原理,探究进程保活的方案和程序异常处理的方法。 一、原理篇...
上一篇(Android 性能优化(三)认识异常Exception和错误Error)讲到造成Crash的原因却有很多,比如:运行时异常的空指针、数组越界、未实例化、强制类型、低内存机制等等,有些时候我们在开发测试阶段都没有出现...