Android 重寫係統Crash處理類,保存Crash信息到SD卡 和 完美退出程序的方法
轉載時注明地址:https://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)
- <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處理類
- 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());
- }
- }
3.接下來我們寫一個MainActivity,它裏麵存在一個導致程序Crash的空指針異常
- 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");
- }
- }
- 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,遭遇異常的時候程序退不出去,然後我寫了一個程序退出的工具類,直接用這個工具類就能很好在任何地方退出程序,工具類如下
- <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就行了
- android.os.Process.killProcess(android.os.Process.myPid());
- System.exit(1);
- ExitAppUtils.getInstance().exit();
最後更新:2017-04-03 12:55:36