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 + "\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 = "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 + "\n");

        }

        

        // 将异常写入到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编程思想后理解的结果,不知道对不对,在以后的学习中慢体会吧。有错误的地方,还请留言。

 

你可能感兴趣的:(android)