zoukankan      html  css  js  c++  java
  • 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("
    ");
            }  
            
            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在CODE上查看代码片派生到我的代码片
     
    1. android.os.Process.killProcess(android.os.Process.myPid());    
    2.         System.exit(1);  

    替换成

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. ExitAppUtils.getInstance().exit();  

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

    代码下载

  • 相关阅读:
    Python入门11 —— 基本数据类型的操作
    Win10安装7 —— 系统的优化
    Win10安装6 —— 系统的激活
    Win10安装5 —— 系统安装步骤
    Win10安装4 —— 通过BIOS进入PE
    Win10安装2 —— 版本的选择与下载
    Win10安装1 —— 引言与目录
    Win10安装3 —— U盘启动工具安装
    虚拟机 —— VMware Workstation15安装教程
    Python入门10 —— for循环
  • 原文地址:https://www.cnblogs.com/changkai244/p/4110371.html
Copyright © 2011-2022 走看看