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
plain
copy

  1. <span style="font-size:12px;">package com.example.customcrash;  
  2. import java.io.File;  
  3. import java.io.FileOutputStream;  
  4. import java.io.PrintWriter;  
  5. import java.io.StringWriter;  
  6. import java.lang.Thread.UncaughtExceptionHandler;  
  7. import java.text.SimpleDateFormat;  
  8. import java.util.Date;  
  9. import java.util.HashMap;  
  10. import java.util.Map;  
  11. import java.util.TimeZone;  
  12.   
  13. import android.content.Context;  
  14. import android.content.pm.PackageInfo;  
  15. import android.content.pm.PackageManager;  
  16. import android.content.pm.PackageManager.NameNotFoundException;  
  17. import android.os.Build;  
  18. import android.os.Environment;  
  19. import android.os.Looper;  
  20. import android.util.Log;  
  21. import android.widget.Toast;  
  22.   
  23. /** 
  24.  * 自定义系统的Crash捕捉类,用Toast替换系统的对话框 
  25.  * 将软件版本信息,设备信息,出错信息保存在sd卡中,你可以上传到服务器中 
  26.  * @author xiaanming 
  27.  * 
  28.  */  
  29. public class CustomCrashHandler implements UncaughtExceptionHandler {  
  30.     private static final String TAG = "Activity";  
  31.     private Context mContext;  
  32.     private static final String SDCARD_ROOT = Environment.getExternalStorageDirectory().toString();  
  33.     private static CustomCrashHandler mInstance = new CustomCrashHandler();  
  34.       
  35.       
  36.     private CustomCrashHandler(){}  
  37.     /** 
  38.      * 单例模式,保证只有一个CustomCrashHandler实例存在 
  39.      * @return 
  40.      */  
  41.     public static CustomCrashHandler getInstance(){  
  42.         return mInstance;  
  43.     }  
  44.   
  45.     /** 
  46.      * 异常发生时,系统回调的函数,我们在这里处理一些操作 
  47.      */  
  48.     @Override  
  49.     public void uncaughtException(Thread thread, Throwable ex) {  
  50.         //将一些信息保存到SDcard中  
  51.         savaInfoToSD(mContext, ex);  
  52.           
  53.         //提示用户程序即将退出  
  54.         showToast(mContext, "很抱歉,程序遭遇异常,即将退出!");  
  55.         try {  
  56.             thread.sleep(2000);  
  57.         } catch (InterruptedException e) {  
  58.             e.printStackTrace();  
  59.         }  
  60. //      android.os.Process.killProcess(android.os.Process.myPid());    
  61. //        System.exit(1);  
  62.           
  63.         //完美退出程序方法  
  64.         ExitAppUtils.getInstance().exit();  
  65.           
  66.     }  
  67.   
  68.       
  69.     /** 
  70.      * 为我们的应用程序设置自定义Crash处理 
  71.      */  
  72.     public void setCustomCrashHanler(Context context){  
  73.         mContext = context;  
  74.         Thread.setDefaultUncaughtExceptionHandler(this);  
  75.     }  
  76.       
  77.     /** 
  78.      * 显示提示信息,需要在线程中显示Toast 
  79.      * @param context 
  80.      * @param msg 
  81.      */  
  82.     private void showToast(final Context context, final String msg){  
  83.         new Thread(new Runnable() {  
  84.               
  85.             @Override  
  86.             public void run() {  
  87.                 Looper.prepare();  
  88.                 Toast.makeText(context, msg, Toast.LENGTH_LONG).show();  
  89.                 Looper.loop();  
  90.             }  
  91.         }).start();  
  92.     }  
  93.       
  94.       
  95.     /** 
  96.      * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中 
  97.      * @param context 
  98.      * @return 
  99.      */  
  100.     private HashMap<String, String> obtainSimpleInfo(Context context){  
  101.         HashMap<String, String> map = new HashMap<String, String>();  
  102.         PackageManager mPackageManager = context.getPackageManager();  
  103.         PackageInfo mPackageInfo = null;  
  104.         try {  
  105.             mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);  
  106.         } catch (NameNotFoundException e) {  
  107.             e.printStackTrace();  
  108.         }  
  109.           
  110.         map.put("versionName", mPackageInfo.versionName);  
  111.         map.put("versionCode", "" + mPackageInfo.versionCode);  
  112.           
  113.         map.put("MODEL", "" + Build.MODEL);  
  114.         map.put("SDK_INT", "" + Build.VERSION.SDK_INT);  
  115.         map.put("PRODUCT", "" +  Build.PRODUCT);  
  116.           
  117.         return map;  
  118.     }  
  119.       
  120.       
  121.     /** 
  122.      * 获取系统未捕捉的错误信息 
  123.      * @param throwable 
  124.      * @return 
  125.      */  
  126.     private String obtainExceptionInfo(Throwable throwable) {  
  127.         StringWriter mStringWriter = new StringWriter();  
  128.         PrintWriter mPrintWriter = new PrintWriter(mStringWriter);  
  129.         throwable.printStackTrace(mPrintWriter);  
  130.         mPrintWriter.close();  
  131.           
  132.         Log.e(TAG, mStringWriter.toString());  
  133.         return mStringWriter.toString();  
  134.     }  
  135.       
  136.     /** 
  137.      * 保存获取的 软件信息,设备信息和出错信息保存在SDcard中 
  138.      * @param context 
  139.      * @param ex 
  140.      * @return 
  141.      */  
  142.     private String savaInfoToSD(Context context, Throwable ex){  
  143.         String fileName = null;  
  144.         StringBuffer sb = new StringBuffer();  
  145.           
  146.         for (Map.Entry<String, String> entry : obtainSimpleInfo(context).entrySet()) {  
  147.             String key = entry.getKey();  
  148.             String value = entry.getValue();  
  149.             sb.append(key).append(" = ").append(value).append("\n");  
  150.         }    
  151.           
  152.         sb.append(obtainExceptionInfo(ex));  
  153.           
  154.         if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){  
  155.             File dir = new File(SDCARD_ROOT + File.separator + "crash" + File.separator);  
  156.             if(! dir.exists()){  
  157.                 dir.mkdir();  
  158.             }  
  159.               
  160.             try{  
  161.                 fileName = dir.toString() + File.separator + paserTime(System.currentTimeMillis()) + ".log";  
  162.                 FileOutputStream fos = new FileOutputStream(fileName);  
  163.                 fos.write(sb.toString().getBytes());  
  164.                 fos.flush();  
  165.                 fos.close();  
  166.             }catch(Exception e){  
  167.                 e.printStackTrace();  
  168.             }  
  169.               
  170.         }  
  171.           
  172.         return fileName;  
  173.           
  174.     }  
  175.       
  176.       
  177.     /** 
  178.      * 将毫秒数转换成yyyy-MM-dd-HH-mm-ss的格式 
  179.      * @param milliseconds 
  180.      * @return 
  181.      */  
  182.     private String paserTime(long milliseconds) {  
  183.         System.setProperty("user.timezone", "Asia/Shanghai");  
  184.         TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");  
  185.         TimeZone.setDefault(tz);  
  186.         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
  187.         String times = format.format(new Date(milliseconds));  
  188.           
  189.         return times;  
  190.     }  
  191. }</span><span style="font-size: 14px;">  
  192. </span>  

上面保存信息到SD卡中需要添加权限<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2.我们需要重写Application类,在onCreate()方法中为它设置Crash处理类

[java] view
plain
copy

  1. package com.example.customcrash;  
  2.   
  3. import android.app.Application;  
  4.   
  5. public class CrashApplication extends Application {  
  6.   
  7.     @Override  
  8.     public void onCreate() {  
  9.         super.onCreate();  
  10.         CustomCrashHandler mCustomCrashHandler = CustomCrashHandler.getInstance();  
  11.         mCustomCrashHandler.setCustomCrashHanler(getApplicationContext());  
  12.     }  
  13.   
  14. }  

我们需要在AndroidManifest.xml指定Application为CrashApplication,

3.接下来我们写一个MainActivity,它里面存在一个导致程序Crash的空指针异常

[java] view
plain
copy

  1. package com.example.customcrash;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.widget.TextView;  
  6.   
  7. public class MainActivity extends Activity {  
  8.     TextView mTextView;  
  9.   
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.           
  15.         mTextView.setText("crash");  
  16.     }  
  17. }  

运行程序,你会发现一句很友好的“很抱歉,程序遭遇异常,即将退出”代替了冷冰冰的警告框,我们打开DDMS,在mnt/sdcard/Crash/目录下面发现了有一个文件,打开文件,我们可以看到

[plain] view
plain
copy

  1. versionCode = 1  
  2. PRODUCT = sdk  
  3. MODEL = sdk  
  4. versionName = 1.0  
  5. SDK_INT = 8  
  6. java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.customcrash/com.example.customcrash.MainActivity}: java.lang.NullPointerException  
  7.     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2663)  
  8.     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)  
  9.     at android.app.ActivityThread.access$2300(ActivityThread.java:125)  
  10.     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)  
  11.     at android.os.Handler.dispatchMessage(Handler.java:99)  
  12.     at android.os.Looper.loop(Looper.java:123)  
  13.     at android.app.ActivityThread.main(ActivityThread.java:4627)  
  14.     at java.lang.reflect.Method.invokeNative(Native Method)  
  15.     at java.lang.reflect.Method.invoke(Method.java:521)  
  16.     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)  
  17.     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)  
  18.     at dalvik.system.NativeStart.main(Native Method)  
  19. Caused by: java.lang.NullPointerException  
  20.     at com.example.customcrash.MainActivity.onCreate(MainActivity.java:15)  
  21.     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
  22.     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)  
  23.     ... 11 more  

说到这里,算是说完了,接下来我们只需要将我们的错误文件上传到服务器就行了。

感谢4楼的朋友提出文章的bug,遭遇异常的时候程序退不出去,然后我写了一个程序退出的工具类,直接用这个工具类就能很好在任何地方退出程序,工具类如下

[java] view
plain
copy

  1. <span style="font-size:12px;">package com.example.customcrash;  
  2.   
  3. import java.util.LinkedList;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7.   
  8. /** 
  9.  * android退出程序的工具类,使用单例模式 
  10.  * 1.在Activity的onCreate()的方法中调用addActivity()方法添加到mActivityList 
  11.  * 2.你可以在Activity的onDestroy()的方法中调用delActivity()来删除已经销毁的Activity实例 
  12.  * 这样避免了mActivityList容器中有多余的实例而影响程序退出速度 
  13.  * @author xiaanming 
  14.  * 
  15.  */  
  16. public class ExitAppUtils {  
  17.     /** 
  18.      * 转载Activity的容器 
  19.      */  
  20.     private List<Activity> mActivityList = new LinkedList<Activity>();  
  21.     private static ExitAppUtils instance = new ExitAppUtils();  
  22.       
  23.     /** 
  24.      * 将构造函数私有化 
  25.      */  
  26.     private ExitAppUtils(){};  
  27.       
  28.     /** 
  29.      * 获取ExitAppUtils的实例,保证只有一个ExitAppUtils实例存在 
  30.      * @return 
  31.      */  
  32.     public static ExitAppUtils getInstance(){  
  33.         return instance;  
  34.     }  
  35.   
  36.       
  37.     /** 
  38.      * 添加Activity实例到mActivityList中,在onCreate()中调用 
  39.      * @param activity 
  40.      */  
  41.     public void addActivity(Activity activity){  
  42.         mActivityList.add(activity);  
  43.     }  
  44.       
  45.     /** 
  46.      * 从容器中删除多余的Activity实例,在onDestroy()中调用 
  47.      * @param activity 
  48.      */  
  49.     public void delActivity(Activity activity){  
  50.         mActivityList.remove(activity);  
  51.     }  
  52.       
  53.       
  54.     /** 
  55.      * 退出程序的方法 
  56.      */  
  57.     public void exit(){  
  58.         for(Activity activity : mActivityList){  
  59.             activity.finish();  
  60.         }  
  61.           
  62.         System.exit(0);  
  63.     }  
  64.       
  65.   
  66. }</span><span style="font-size: 14px;">  
  67. </span>  

 退出工具类的使用我在代码中说的还比较清楚,相信你很容易使用,然后将CustomCrashHandler类uncaughtException(Thread thread, Throwable ex)方法中的

注:如果你的工程有很多个Activity,你需要在每一个Activity的onCreate()和onDestroy()调用addActivity()和delActivity()方法,这样子是不是显得很多余?你可以写一个BaseActivity继承Activity,然后再BaseActivity的onCreate()和onDestroy()调用addActivity()和delActivity()方法,其他的Activity继承BaseActivity就行了

[java] view
plain
copy

  1. android.os.Process.killProcess(android.os.Process.myPid());    
  2.         System.exit(1);  

替换成

[java] view
plain
copy

  1. ExitAppUtils.getInstance().exit();  
时间: 2024-10-29 18:31:40

Android 重写系统Crash处理类,保存Crash信息到SD卡 和 完美退出程序的方法的相关文章

Android实现从网络获取图片显示并保存到SD卡的方法_Android

本文实例讲述了Android实现从网络获取图片显示并保存到SD卡的方法.分享给大家供大家参考,具体如下: 问题: 如何不断获取图片并显示出来,达到视频的效果? 代码: public class GetPictureFromInternetActivity extends Activity { private ImageView imageView; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInst

Android判断SD卡是否已经挂载的方法_Android

本文实例讲述了Android判断SD卡是否已经挂载的方法.分享给大家供大家参考.具体如下: 提供一个监听方法BroadcastReceiver 设置IntentFilter为: Intent.ACTION_MEDIA_MOUNTED Intent.ACTION_MEDIA_EJECT Intent.ACTION_MEDIA_REMOVED  然后再public void onReceive(Context context, Intent intent) 中实现你的启动逻辑startActivit

Android获取assets文件夹中的数据并写入SD卡示例_Android

本文示例主要实现了Android获取assets文件夹中的数据并将其写入到SD卡中,该程序实现的步骤主要为:首先读取assets文件夹中的数据库,再将其写入到SD存储卡中. 完整示例代码如下: import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import android.content.Context; /*将assets文件

Android获取assets文件夹中的数据并写入SD卡示例

本文示例主要实现了Android获取assets文件夹中的数据并将其写入到SD卡中,该程序实现的步骤主要为:首先读取assets文件夹中的数据库,再将其写入到SD存储卡中. 完整示例代码如下: import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import android.content.Context; /*将assets文件

Android 中Crash时如何获取异常信息详解及实例

Android 中Crash时如何获取异常信息详解 前言: 大家都知道,Android应用不可避免的会发生crash,无论你的程序写的多完美,总是无法完全避免crash的发生,可能是由于Android系统底层的bug,也可能是由于不充分的机型适配或者是糟糕的网络状况.当crash发生时,系统会kill掉你的程序,表现就是闪退或者程序已停止运行,这对用户来说是很不友好的,也是开发者所不愿意看到的,更糟糕的是,当用户发生了crash,开发者却无法得知程序为何crash,即便你想去解决这个crash,

【ANDROID游戏开发十二】(保存游戏数据 [上文])详解SHAREDPREFERENCE 与 FILEINPUTSTREAM/FILEOUTPUTSTREAM将数据存储到SD卡中!

本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/327.html ----------------------- 『很多童鞋说我的代码运行后,点击home或者back后会程序异常,如果你也这样遇到过,那么你肯定没有仔细读完Himi的博文,第十九篇Himi专门写了关于这些错误的原因和解决方法,这里我在博客都补充说明下,省的童鞋们总疑惑这一块:请点击下面联系进入阅读:

Android 如何收集已发布程序的崩溃信息

下面我就说说如何收集程序运行过程的异常信息.需要的朋友可以过来参考下   我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃.不过理想是丰满的,现实是骨感的.没有一个程序员能 保证自己写的程序绝对不会出现异常崩溃.特别是针对用户数达到几十万几百万的程序,当你用户数达到一定数量级后,就算你的程序出现个别异常崩溃情况也不用 惊讶. 既然我们写的程序都有可能发生异常崩溃,如果是还没发布的程序,我们可以通过测试抓取Log来分析.不过针对已经发布的程序,我们没法重现现

Android 如何收集已发布程序的崩溃信息_Android

我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃.不过理想是丰满的,现实是骨感的.没有一个程序员能保证自己写的程序绝对不会出现异常崩溃.特别是针对用户数达到几十万几百万的程序,当你用户数达到一定数量级后,就算你的程序出现个别异常崩溃情况也不用惊讶. 既然我们写的程序都有可能发生异常崩溃,如果是还没发布的程序,我们可以通过测试抓取Log来分析.不过针对已经发布的程序,我们没法重现现象,所以让用户反馈程序异常信息就很重要.下面我们说说如何收集程序运行过程的异常

Android 数据库SQLite 写入SD卡的方法_Android

如果手机没有root,数据库文件是无法查看到的,不方便调试. 最好的办法是把数据库写进SD卡. 修改的地方有两处: 1.在你的helper类中把数据库文件名称 DATABASE_NAME 由原来的一个文件名,修改成路径的形式. 修改前:DATABASE_NAME = "demo.db" public class MyDBHelper extends SQLiteOpenHelper { public static final int VERSION = 1; //数据库版本号 publ