• Android程序捕获未处理异常,处理与第三方方法冲突时的异常传递


    自己的android程序对异常进行了处理,用的也是网上比较流行的CrashHandler,代码如下,就是出现了未处理的异常程序退出,并收集收集设备信息和错误信息仪器保存到SD卡,这里没有上传到服务器。

    public class CrashHandler implements UncaughtExceptionHandler
    {
    
        public static final String TAG = "CrashHandler";
    
        // CrashHandler 实例
        private static CrashHandler INSTANCE = new CrashHandler();
    
        // 程序的 Context 对象
        private Context mContext;
    
        // 系统默认的 UncaughtException 处理类
        private Thread.UncaughtExceptionHandler mDefaultHandler;
    
        // 用来存储设备信息和异常信息
        private Map<String, String> infos = new HashMap<String, String>();
    
        // 用于格式化日期,作为日志文件名的一部分
        private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    
        /** 保证只有一个 CrashHandler 实例 */
        private CrashHandler()
        {
        }
    
        /** 获取 CrashHandler 实例 ,单例模式 */
        public static CrashHandler 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)
            {
                // 如果用户没有处理则让系统默认的异常处理器来处理
                LogUtil.i("wepa mDefaultHandler beg");
                mDefaultHandler.uncaughtException(thread, ex);
            } else
            {
                try
                {
                    Thread.sleep(2000);
                } catch (InterruptedException e)
                {
                    LogUtil.e("error : ", e);
                }
                LogUtil.i("wepa killProcess beg");
                // 退出程序
                // 清除栈
                List<Activity> openedActivity = ((WepaApplication) mContext)
                        .getOpenedActivity();
                for (Activity activity : openedActivity)
                {
                    if (activity != null)
                    {
                        LogUtil.d("activity getComponentName : "
                                + activity.getComponentName());
                        activity.finish();
                    }
                }
                android.os.Process.killProcess(android.os.Process.myPid());
                System.exit(1);
    
                /*
                 * // 重新启动程序,注释上面的退出程序 Intent intent = new Intent();
                 * intent.setClass(mContext, MainPageActivity.class);
                 * intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 * mContext.startActivity(intent);
                 * android.os.Process.killProcess(android.os.Process.myPid());
                 */
            }
        }
    
        /**
         * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
         * 
         * @param ex
         * @return true:如果处理了该异常信息;否则返回 false
         */
        private boolean handleException(Throwable ex)
        {
            if (ex == null)
            {
                return false;
            }
            LogUtil.i("wepa handleException beg");
            new Thread()
            {
                @Override
                public void run()
                {
                    Looper.prepare();
                    Toast.makeText(mContext, "很抱歉,程序遇到异常,即将退出", Toast.LENGTH_SHORT)
                            .show();
                    Looper.loop();
                }
            }.start();
            // 收集设备参数信息
            collectDeviceInfo(mContext);
            // 保存日志文件
            saveCrashInfo2File(ex);
            // 使用 Toast 来显示异常信息
            LogUtil.i("wepa saveCrashInfo2File OK");
            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)
            {
                LogUtil.e("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());
                    LogUtil.d(field.getName() + " : " + field.get(null));
                } catch (Exception e)
                {
                    LogUtil.e("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 + "
    ");
            }
    
            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 = Environment.getExternalStorageDirectory()
                            + "/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)
            {
                LogUtil.e("an error occured while writing file...", e);
            }
    
            return null;
        }
    }
    View Code

    使用方法就是在自己的Application类中加入初始化就好了,代码如下:

    // 在自己的Application中的OnCreate中加入异常处理的代码
    public void onCreate()
        {
            // TODO Auto-generated method stub
            super.onCreate();
    
            // 异常捕获处理
            initErrorHandler();
                   // 其他处理
    
        }
    
            /**
         * 处理错误
         */
        private void initErrorHandler()
        {
            CrashHandler handler = CrashHandler.getInstance();
            handler.init(getApplicationContext());
        }
    View Code

    这样就可以保证程序遇到未知错误时推出app。

    后来有需求要加个第三方的插件进来,这个插件中也要在自定义的Application类的OnCreate中初始化,而且它也有对异常程序的默认处理,也需要保存错错误文件,用的份上面的CrashHandler是一样的代码,这就出现了问题,因为Thread.setDefaultUncaughtExceptionHandler(this);方法将Thread里的静态变量defaultUncaughtHandler设置的话,以后的Thread默认就是它处理了。

     private static UncaughtExceptionHandler defaultUncaughtHandler;

    因此,需要一种传递机制,使得两个异常中定义的handleException方法(里面是异常信息的保存)都能进行,这里因为第三放的插件不能对app的异常造成干扰,只让它保存异常就可以了,所以在handleException方法中返回false,然后将异常向上传递,这使得第三方插件中定义的mDefaultHandler.uncaughtException(thread, ex);得以执行,再根据插件public void init(Context context)函数中的定义顺序

            mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
            
            // 设置该CrashHandler为程序的默认处理器,人为捕获该异常
            Thread.setDefaultUncaughtExceptionHandler(this);
    mDefaultHandler 是上一次设置的值,所以只需要这个值是我们app的异常处理就好了。因此在自定义Application中初始化异常CrashHandler的时候顺序就必须要满足初始自己app的,再初始第三方插件的。这样就可以保证异常先由插件处理,由于返回false,再由app中自己的处理,最后返回true,并终止程序。
    Application中初始化如下:
        public void onCreate()
        {
            // app的异常捕获处理
            initErrorHandler();
                    // 插件的异常捕获处理
            if (Config.isOpen)
            {
                AnalyticsManager.init(getApplicationContext());
            }
    
        }
    View Code

    其中插件中也使用的CrashHandlelr,定义差不多,就是返回值不同,然后不终止程序。代码如下

    public class CrashHandler implements UncaughtExceptionHandler
    {
    
        public static final String TAG = "CrashHandler";
    
        // 系统默认的UncaughtException处理类
        private Thread.UncaughtExceptionHandler mDefaultHandler;
        // CrashHandler实例
        private static CrashHandler INSTANCE = new CrashHandler();
        // 程序的Context对象
        private Context mContext;
        // 用来存储设备信息和异常信息
        private Map<String, String> infos = new HashMap<String, String>();
    
        // 用于格式化日期,作为日志文件名的一部分
        private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    
        /** 保证只有一个CrashHandler实例 */
        private CrashHandler()
        {
        }
    
        /** 获取CrashHandler实例 ,单例模式 */
        public static CrashHandler 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();
    
            // 保存日志文件
            saveCrashInfo2File(ex);
            return false;
        }
    
        /**
         * 保存错误信息到文件中
         * 
         * @param ex
         * @return 返回文件名称,便于将文件传送到服务器
         */
        private void 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 + "
    ");
            }
            
            // 将异常写入到PrintWriter
            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(timestamp);
                BehaviorManager.saveException(mContext, time, sb.toString());
    
                // test for exception
                String fileName = Environment.getExternalStorageDirectory().toString()
                        + File.separator + "excep/" + "crush_instant_";
                Date date = new Date(System.currentTimeMillis());
                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
                final String fileFullName = fileName + df.format(date) + "_" +System.currentTimeMillis() + ".log";
                Log.i("Wepa", "SDK saveDataToFile OK...");
                System.out.println("SDK saveDataToFile OK...");
                NetworkManager.saveDataToFile(fileFullName, sb.toString());
            }
            catch (Exception e)
            {
                Log.e(TAG, "an error occured while writing file...", e);
            }
        }
    }
    View Code

    这样就可以有两份异常文件了,插件的可以自行处理,比较灵活。

    学习了Thread的默认异常处理,还有传播方法,虽然耗费了一下午的时间还是很值得的,开始只是尝试,后来才明白道理,感觉很好。

    不过,这是看了Java编程思想后理解的结果,不知道对不对,在以后的学习中慢体会吧。有错误的地方,还请留言。

     
    MissR:stay
  • 相关阅读:
    P3193 [HNOI2008]GT考试(KMP+矩阵乘法加速dp)
    P2606 [ZJOI2010]排列计数
    洛谷P2657 [SCOI2009]windy数
    P2602 [ZJOI2010]数字计数(递推)
    洛谷 P1073 最优贸易
    [一本通学习笔记] 字典树与 0-1 Trie
    [一本通学习笔记] KMP算法
    [一本通学习笔记] 字符串哈希
    [一本通学习笔记] 广度优先搜索和优化
    [一本通学习笔记] 深度优先搜索与剪枝
  • 原文地址:https://www.cnblogs.com/permanent2012moira/p/4589468.html
Copyright © 2020-2023  润新知