Android中处理崩溃闪退错误

Android中处理崩溃闪退异常

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

  我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。不过首先我们还是来了解以下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。

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

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

AppException.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:

package cn.com.ista.pdachina.app;

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;  

/**
 * AppException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 *
 * @author user
 *
 */
public class AppException implements UncaughtExceptionHandler {  

    public static final String TAG = "AppException";  

    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static AppException INSTANCE = new AppException();
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<String, String>();  

    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  

    /** 保证只有一个AppEeception实例 */
    private AppException() {
    }  

    /** 获取AppException实例 ,单例模式 */
    public static AppException 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 = "log-" + time + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/PdaChina/";
                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;
    }
}  

完成这个AppException后,我们需要在一个Application环境中让其运行,为此,我们继承android.app.Application,添加自己的代码,AppContext.java代码如下:

package cn.com.ista.pdachina.app;

import android.app.Application;
import android.content.Context;
/**
 * 全局获取上下文类:用于保存和调用全局应用配置及访问网络数据
 * @author guopeng
 * @version 1.0
 * @created 2015-10-26
 */
public class AppContext extends Application {

    private static Context instance;

    @Override
    public void onCreate()
    {
        instance = getApplicationContext();

        AppException appException = AppException.getInstance();
        appException.init(instance);
    }

    public static Context getContext()
    {
        return instance;
    }

}

最后,为了让我们的AppContext取代android.app.Application的地位,在我们的代码中生效,我们需要修改AndroidManifest.xml:

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

因为我们上面的AppException中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:

<!-- SD卡读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 

大功告成

时间: 2024-08-27 09:49:47

Android中处理崩溃闪退错误的相关文章

升级iOS10之后调用摄像头/麦克风等硬件程序崩溃闪退的问题

在升级到iOS10之后, 开发过程中难免会遇到很多的坑, 下面是一些常见的坑, 我做了一些整理, 希望对大家开发有帮助: &1. 调用视频,摄像头, 麦克风,等硬件程序崩溃闪退的问题: 要注意的问题 iOS10 对隐私权限的管理更为严格 ,比如访问的摄像头.麦克风等硬件,都需要提前请求应用权限.允许后才可以使用,或者现在要提前声明,虽然以往要求不严格. 在iOS10中比如遇到崩溃,日志: *This app has crashed because it attempted to access p

解决Xilinx_ISE在Win8下打开崩溃闪退的方法

解决Xilinx_ISE在Win8下打开崩溃闪退的方法 在64位windows8或者8.1上安装xilinx ise之后,加载 licence或者保存文件的时候,ise应用程序就会崩溃,出现闪退的情况. 修复方法: 第一步: 找到xilinx安装文件下的子文件,我的是安装在D盘. [plain] view plaincopy D:\Xilinx\14.4\ISE_DS\ISE\lib\nt64 在这个文件夹中搜索文件 libPortability 会出来两个文件 [plain] view pla

android studio安装后闪退

最近想把以前项目的android代码工程从Eclipse 转移到android studio中,下载谷歌最近的android studio安装包2.3版本安装后,打开android studio,程序闪退,在网上也找了好多答案,可是一一试过后,都不能用,最近FQ到google,搜索,找到问题原因,原因是JDK版本太低,我本机原来的JDK版本是1.7,随后下载安装JDK1.8,安装后,可以正常打开android studio. 转载此文,请注明来源,谢谢

手游频繁崩溃”闪退”? 从程序上找原因

游戏程序 平台类型: iOS Android  程序设计: 算法逻辑/智能AI 服务器 数据库  编程语言: C/C++ Java  引擎/SDK: 其它  <ignore_js_op> 文 / 网易 Tjay(QA) 作为玩家,当游戏crash的时候是什么心情,如果这个游戏玩起来还不错的话,那我可能还会打开第二次,如果这个游戏一般的话我可能直接怒删了.当多次出现闪退crash的时候,这种糟糕的体验很容易让用户流失,造成很大的损失.但是作为测试人员,面对如此棘手的事情,首先要做的是协助开发组解

未捕获异常,现实程序崩溃闪退

碰到程序崩溃时,闪退效果,不会提示"xxx程序异常,退出程序".这样的效果就要使用到未捕获异常来实现,这里记录了我的一个写法.其实原理很简单,设置程序的未捕获异常监听,实现监听的一个方法,在该方法中现实直接没有提示的退出程序. 捕获异常工具类 package com.tdh.http; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHan

解决ubuntu上在androidstudio中启动emulator闪退的问题

作者 彭东林 [email protected] 平台 Ubuntu14.04 64 androidstudio 2.3.3 现象 在创建好模拟器后,点击启动时,模拟器界面刚出来就闪退了 解决 由于是在图形界面点击启动的,看不到任何出错的log,所以我们需要使用命令行启动emulator的方式,这样会把出错的信息打印出来. 1.查看我们创建的模拟器的名字: 从上图看到模拟器的名字是: 32_QVGA_ADP2_API_25 2.找到android sdk中模拟器 对于我的机器,模拟器存放在/ho

setSupportActionBar(toolbar)导致程序崩溃闪退

最近在做一个项目,使用了第三方的开源项目,主要是想实现android5.0之后推出的MaterialDesign的风格,但是代码已经写好了,发现一运行就闪退,所以就开始debug,发现问题出现在 1 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 2 setSupportActionBar(toolbar); 很显然应该是在第二行出错了,再根据logcat上的日志: This Activity already has an acti

android 中处理崩溃异常并重启程序

有时候由于测试不充分或者程序潜在的问题而导致程序异常崩溃,这个是令人无法接受的,在Android中怎样捕获程序的异常崩溃,然后进行一些必要的处理或重新启动 应用这个问题困恼了我很久,今天终于解决了该问题,写篇文章记录一下. 首先捕获程序崩溃的异常就必须了解一下Java中UncaughtExceptionHandler这个接口,android沿用了此接口,在android API中: 通过实现此接口,能够处理线程被一个无法捕捉的异常所终止的情况.如上所述的情况,handler将会报告线程终止和不明

Android中处理崩溃异常CrashHandler

来源:http://blog.csdn.net/liuhe688/article/details/6584143 大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信