Android程序捕獲未處理異常,處理與第三方方法衝突時的異常傳遞

本身的android程序對異常進行了處理,用的也是網上比較流行的CrashHandler,代碼以下,就是出現了未處理的異常程序退出,並收集收集設備信息和錯誤信息儀器保存到SD卡,這裏沒有上傳到服務器。android

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默認就是它處理了。app

 private static UncaughtExceptionHandler defaultUncaughtHandler;

所以,須要一種傳遞機制,使得兩個異常中定義的handleException方法(裏面是異常信息的保存)都能進行,這裏由於第三放的插件不能對app的異常形成干擾,只讓它保存異常就能夠了,因此在handleException方法中返回false,而後將異常向上傳遞,這使得第三方插件中定義的mDefaultHandler.uncaughtException(thread, ex);得以執行,再根據插件public void init(Context context)函數中的定義順序ide

        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

這樣就能夠有兩份異常文件了,插件的能夠自行處理,比較靈活。oop

學習了Thread的默認異常處理,還有傳播方法,雖然耗費了一下午的時間仍是很值得的,開始只是嘗試,後來才明白道理,感受很好。學習

不過,這是看了Java編程思想後理解的結果,不知道對不對,在之後的學習中慢體會吧。有錯誤的地方,還請留言。ui

相關文章
相關標籤/搜索