• 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();  

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

    代码下载

  • 相关阅读:
    Revolving Digits[EXKMP]
    字符加密Cipher(bzoj 1031)
    Hotaru's problem
    1089 最长回文子串 V2(Manacher算法)
    3172: [Tjoi2013]单词
    3689: 异或之
    3942: [Usaco2015 Feb]Censoring [KMP]
    2795: [Poi2012]A Horrible Poem
    GT考试(bzoj 1009)
    NOIP2016提高组解题报告
  • 原文地址:https://www.cnblogs.com/changkai244/p/4110371.html
Copyright © 2020-2023  润新知