Android_ 重写系统Crash处理类,保存Crash信息到SD卡 和 完美退出程序的方法

转载时注明地址:http://blog.csdn.net/xiaanming/article/details/9344703

我们开发Android应用的时候,当出现Crash的时候,系统弹出一个警告框,如下图一,有些手机会黑屏几秒钟然后还伴随着振动,作为我们开发人员,是很讨厌这样子的Crash,因为这意味着我们又要改bug,每个程序员都希望自己开发出来的东西bug少点,稳定点,但是没有bug的程序几乎是不可能的,作为用户,如果出现这样子的警告框,他的心情也会很不爽,也许还会破口大骂,如果用图二来提示用户是不是感觉会好一点

一句简简单单的“很抱歉,程序遭遇异常,即将退出”是不是更有人情味,人们对道歉的话是永远不会嫌腻的,哈哈!当然我们自定义Carsh处理类不仅仅是将系统警告框替换成Toast,还有更重要的原因,我们都知道市场上的Android设备和系统琳琅满目,参差不齐的,如果我们购买市场上每一种Android设备来测试,这是不现实的,况且这其中购买设备的资金也不小,所以我们重写Crash处理类,当我们的用户发生Crash的时候,我们将设备信息和错误信息保存起来,然后再上传到服务器中,这对于我们是极其有帮助的,特别是个人开发用户和小公司,因为他们的测试可能相对于大公司来说会显得不那么专业,就比如我们公司吧,自己测试,然后我们老大也要我做这个功能,我就将捕获异常和保存异常信息和大家分享下,上传到服务器功能的大家自行实现

先看下项目结构

1.我们新建一个CustomCrashHandler类 实现UncaughtExceptionHandler接口,重写回调方法void uncaughtException(Thread thread, Throwable ex)

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

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;

/**
 * 自定义系统的Crash捕捉类,用Toast替换系统的对话框
 * 将软件版本信息,设备信息,出错信息保存在sd卡中,你可以上传到服务器中
 * @author xiaanming
 *
 */
public class CustomCrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = "Activity";
    private Context mContext;
    private static final String SDCARD_ROOT = Environment.getExternalStorageDirectory().toString();
    private static CustomCrashHandler mInstance = new CustomCrashHandler();

    private CustomCrashHandler(){}
    /**
     * 单例模式,保证只有一个CustomCrashHandler实例存在
     * @return
     */
    public static CustomCrashHandler getInstance(){
        return mInstance;
    }

    /**
     * 异常发生时,系统回调的函数,我们在这里处理一些操作
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        //将一些信息保存到SDcard中
        savaInfoToSD(mContext, ex);

        //提示用户程序即将退出
        showToast(mContext, "很抱歉,程序遭遇异常,即将退出!");
        try {
            thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        android.os.Process.killProcess(android.os.Process.myPid());
//        System.exit(1);

        //完美退出程序方法
        ExitAppUtils.getInstance().exit();

    }

    /**
     * 为我们的应用程序设置自定义Crash处理
     */
    public void setCustomCrashHanler(Context context){
        mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 显示提示信息,需要在线程中显示Toast
     * @param context
     * @param msg
     */
    private void showToast(final Context context, final String msg){
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();
    }

    /**
     * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
     * @param context
     * @return
     */
    private HashMap<String, String> obtainSimpleInfo(Context context){
        HashMap<String, String> map = new HashMap<String, String>();
        PackageManager mPackageManager = context.getPackageManager();
        PackageInfo mPackageInfo = null;
        try {
            mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        map.put("versionName", mPackageInfo.versionName);
        map.put("versionCode", "" + mPackageInfo.versionCode);

        map.put("MODEL", "" + Build.MODEL);
        map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
        map.put("PRODUCT", "" +  Build.PRODUCT);

        return map;
    }

    /**
     * 获取系统未捕捉的错误信息
     * @param throwable
     * @return
     */
    private String obtainExceptionInfo(Throwable throwable) {
        StringWriter mStringWriter = new StringWriter();
        PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
        throwable.printStackTrace(mPrintWriter);
        mPrintWriter.close();

        Log.e(TAG, mStringWriter.toString());
        return mStringWriter.toString();
    }

    /**
     * 保存获取的 软件信息,设备信息和出错信息保存在SDcard中
     * @param context
     * @param ex
     * @return
     */
    private String savaInfoToSD(Context context, Throwable ex){
        String fileName = null;
        StringBuffer sb = new StringBuffer();

        for (Map.Entry<String, String> entry : obtainSimpleInfo(context).entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key).append(" = ").append(value).append("\n");
        }  

        sb.append(obtainExceptionInfo(ex));

        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            File dir = new File(SDCARD_ROOT + File.separator + "crash" + File.separator);
            if(! dir.exists()){
                dir.mkdir();
            }

            try{
                fileName = dir.toString() + File.separator + paserTime(System.currentTimeMillis()) + ".log";
                FileOutputStream fos = new FileOutputStream(fileName);
                fos.write(sb.toString().getBytes());
                fos.flush();
                fos.close();
            }catch(Exception e){
                e.printStackTrace();
            }

        }

        return fileName;

    }

    /**
     * 将毫秒数转换成yyyy-MM-dd-HH-mm-ss的格式
     * @param milliseconds
     * @return
     */
    private String paserTime(long milliseconds) {
        System.setProperty("user.timezone", "Asia/Shanghai");
        TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
        TimeZone.setDefault(tz);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        String times = format.format(new Date(milliseconds));

        return times;
    }
}

上面保存信息到SD卡中需要添加权限<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2.我们需要重写Application类,在onCreate()方法中为它设置Crash处理类

import android.app.Application;

public class CrashApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
CustomCrashHandler mCustomCrashHandler = CustomCrashHandler.getInstance();
mCustomCrashHandler.setCustomCrashHanler(getApplicationContext());
}

}我们需要在AndroidManifest.xml指定Application为CrashApplication,

3.接下来我们写一个MainActivity,它里面存在一个导致程序Crash的空指针异常

package com.example.customcrash;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {
	TextView mTextView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		mTextView.setText("crash");
	}
}

  运行程序,你会发现一句很友好的“很抱歉,程序遭遇异常,即将退出”代替了冷冰冰的警告框,我们打开DDMS,在mnt/sdcard/Crash/目录下面发现了有一个文件,打开文件,我们可以看到

versionCode = 1
PRODUCT = sdk
MODEL = sdk
versionName = 1.0
SDK_INT = 8
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.customcrash/com.example.customcrash.MainActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2663)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
at android.app.ActivityThread.access$2300(ActivityThread.java:125)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.example.customcrash.MainActivity.onCreate(MainActivity.java:15)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
... 11 more

说到这里,算是说完了,接下来我们只需要将我们的错误文件上传到服务器就行了。

感谢4楼的朋友提出文章的bug,遭遇异常的时候程序退不出去,然后我写了一个程序退出的工具类,直接用这个工具类就能很好在任何地方退出程序,工具类如下

import java.util.LinkedList;
import java.util.List;

import android.app.Activity;

/**
 * android退出程序的工具类,使用单例模式
 * 1.在Activity的onCreate()的方法中调用addActivity()方法添加到mActivityList
 * 2.你可以在Activity的onDestroy()的方法中调用delActivity()来删除已经销毁的Activity实例
 * 这样避免了mActivityList容器中有多余的实例而影响程序退出速度
 * @author xiaanming
 *
 */
public class ExitAppUtils {
    /**
     * 转载Activity的容器
     */
    private List<Activity> mActivityList = new LinkedList<Activity>();
    private static ExitAppUtils instance = new ExitAppUtils();

    /**
     * 将构造函数私有化
     */
    private ExitAppUtils(){};

    /**
     * 获取ExitAppUtils的实例,保证只有一个ExitAppUtils实例存在
     * @return
     */
    public static ExitAppUtils getInstance(){
        return instance;
    }

    /**
     * 添加Activity实例到mActivityList中,在onCreate()中调用
     * @param activity
     */
    public void addActivity(Activity activity){
        mActivityList.add(activity);
    }

    /**
     * 从容器中删除多余的Activity实例,在onDestroy()中调用
     * @param activity
     */
    public void delActivity(Activity activity){
        mActivityList.remove(activity);
    }

    /**
     * 退出程序的方法
     */
    public void exit(){
        for(Activity activity : mActivityList){
            activity.finish();
        }

        System.exit(0);
    }

}

退出工具类的使用我在代码中说的还比较清楚,相信你很容易使用,然后将CustomCrashHandler类uncaughtException(Thread thread, Throwable ex)方法中的

注:如果你的工程有很多个Activity,你需要在每一个Activity的onCreate()和onDestroy()调用addActivity()和delActivity()方法,这样子是不是显得很多余?你可以写一个BaseActivity继承Activity,然后再BaseActivity的onCreate()和onDestroy()调用addActivity()和delActivity()方法,其他的Activity继承BaseActivity就行了

[java] view plaincopy

  1. android.os.Process.killProcess(android.os.Process.myPid());
  2. System.exit(1);

替换成

[java] view plaincopy

  1. ExitAppUtils.getInstance().exit();

如果觉得这篇文章对你有用,你就帮我顶一下,谢谢!

代码下载

时间: 2024-10-05 23:39:42

Android_ 重写系统Crash处理类,保存Crash信息到SD卡 和 完美退出程序的方法的相关文章

android基础知识---重写系统Crash处理类保存上传和完美退出程序的方法

当今市场上android的手机型号和版本太多要做到完全适配几乎是完全不可能的,那么怎么才能获取其他的玩家的出错的信息呢!这里我们就要重新定义系统的Crash处理类了. 首先我们我们新建一个CustomCrashHandler类 实现UncaughtExceptionHandler接口,重写回调方法void uncaughtException(Thread thread, Throwable ex) package com.example.admin.crashchuli; import andr

android Loger日志类(获取内置sd卡)

Android手机自带内部存储路径的获取 原文地址:http://my.oschina.net/liucundong/blog/288183 直接贴代码: public static String getExternalSdCardPath() { if (SDCardUtils.isSDCardEnable()) { File sdCardFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath());

工具类总结---(五)---SD卡文件管理

里面注释很清楚了... package cgjr.com.cgjr.utils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.util.Log; import java.io.BufferedInputStream; import java.io

android 文件保存到应用和sd卡中

<span style="font-size:18px;">1.权限添加 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> public static S

Android 获取屏幕截图 和保存到本地的sd卡路径下

/** * 获取和保存当前屏幕的截图 */ private void GetandSaveCurrentImage() { //1.构建Bitmap WindowManager windowManager = getWindowManager(); Display display = windowManager.getDefaultDisplay(); int w = display.getWidth(); int h = display.getHeight(); Bitmap Bmp = Bi

android截屏:保存一个view的内容为图片并存放到SD卡

项目中偶尔会用到截屏分享,于是就有了下面这个截屏的方法~ 下面得saveImage()方法就是保存当前Activity对应的屏幕所有内容的截屏保存. private void saveImage() { // SD卡保存路径 String savePath = Environment.getExternalStorageDirectory() + "/temp.png"; // showProgress("请稍候", "正在保存图片--"); s

bpi 镜像烧写、emmc使用与制作sd卡系统镜像(多次测试可用)

一.bpi 镜像烧写 参考官方wiki http://wiki.banana-pi.org/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B_%E9%A6%99%E8%95%89%E6%B4%BE%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95%E4%B8%8E%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8 1.Windows操作系统下使用SD Formatter格式化SD卡 2.使用Win32 Diskimager烧im

Ubuntu Linux系统包含两类环境变量

Ubuntu Linux系统包含两类环境变量:系统环境变量和用户环境变量.系统环境变量对所有系统用户都有效,用户环境变量仅仅对当前的用户有效. 修改用户环境变量 用户环境变量通常被存储在下面的文件中: ~/.profile ~/.bash_profile 或者 ~./bash_login ~/.bashrc 上述文件在Ubuntu 10.0以前版本不推荐使用. 系统环境变量 系统环境变量一般保存在下面的文件中: /etc/environment /etc/profile /etc/bash.ba

Android 存储学习之保存系统短信到SD卡

本节学习,保存系统短信到SD卡中.既然是要保存系统短信到SD卡中,前提是先要拿到系统的短信,关于如何读取系统的短信,请看我的关于ContentProvider文章: Android 四大组件学习之ContentProvider三 既然知道了任务的目的,那我们就直接实现.我们先将系统的短信读出,然后保存到xml文件中,然后将xml文件写到sd卡中. 1: 先将系统短信读出 //得到ContentResolver ContentResolver cr = getContentResolver();