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)
[java] view plaincopy
<span style="font-size:12px;">package com.example.customcrash;  
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("\n");  
        }    
          
        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;  
    }  
}</span><span style="font-size: 14px;">  
</span>  




上面保存信息到SD卡中需要添加權限<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2.我們需要重寫Application類,在onCreate()方法中爲它設置Crash處理類
[java] view plaincopy
package com.example.customcrash;  
  
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的空指針異常
[java] view plaincopy
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/目錄下面發現了有一個文件,打開文件,我們可以看到
[plain] view plaincopy
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,遭遇異常的時候程序退不出去,然後我寫了一個程序退出的工具類,直接用這個工具類就能很好在任何地方退出程序,工具類如下
[java] view plaincopy
<span style="font-size:12px;">package com.example.customcrash;  
  
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);  
    }  
      
  
}</span><span style="font-size: 14px;">  
</span>  
 退出工具類的使用我在代碼中說的還比較清楚,相信你很容易使用,然後將CustomCrashHandler類uncaughtException(Thread thread, Throwable ex)方法中的
注:如果你的工程有很多個Activity,你需要在每一個Activity的onCreate()和onDestroy()調用addActivity()和delActivity()方法,這樣子是不是顯得很多餘?你可以寫一個BaseActivity繼承Activity,然後再BaseActivity的onCreate()和onDestroy()調用addActivity()和delActivity()方法,其他的Activity繼承BaseActivity就行了
[java] view plaincopy
android.os.Process.killProcess(android.os.Process.myPid());    
        System.exit(1);  
替換成
[java] view plaincopy
ExitAppUtils.getInstance().exit();
發佈了52 篇原創文章 · 獲贊 7 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章