码迷,mamicode.com
首页 > 移动开发 > 详细

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

时间:2015-06-19 20:06:01      阅读:211      评论:0      收藏:0      [点我收藏+]

标签:

自己的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程序捕获未处理异常,处理与第三方方法冲突时的异常传递

标签:

原文地址:http://www.cnblogs.com/permanent2012moira/p/4589468.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!