Android热修复之 - 收集崩溃信息上传服务器

1.概述

  大致的流程就是在用户崩溃的时候,我们获取崩溃信息、应用当前的信息和手机信息,然后把它保存到手机内存卡,再找我就直接找出来看看。后来衍生到上线后某些奇葩机型会有部分问题,所以不得不上传到服务器,后来发现居然可以配合热修复一步一步如此神奇,接下来我们来玩一玩,如何才能把用户的崩溃信息上传到服务器。大家也可以去找腾讯他有现成的: https://bugly.qq.com/v2/index 友盟也有现成的: http://www.umeng.com/ 实现的原理都类似。

2.实现


2.1 拦截闪退信息
  
  如何去收集我们的闪退信息?我们需要认识一下这个类Thread.UncaughtExceptionHandler,一言不和就看源码,这个可以不看,且看我是如何写的。

拦截应用的闪退信息

复制代码
 1 public class ExceptionCrashHandler implements Thread.UncaughtExceptionHandler {
 2 
 3     private static final String TAG = "ExceptionCrashHandler";
 4     // 单例设计模式
 5     private static ExceptionCrashHandler mInstance;
 6     // 留下原来的,便于开发的时候调试
 7     private Thread.UncaughtExceptionHandler mDefaultHandler;
 8     // 上下文  获取版本信息和手机信息
 9     private Context mContext;
10 
11     public static ExceptionCrashHandler getInstance() {
12         if (mInstance == null) {
13             synchronized (ExceptionCrashHandler.class) {
14                 if (mInstance == null) {
15                     mInstance = new ExceptionCrashHandler();
16                 }
17             }
18         }
19         return mInstance;
20     }
21 
22     private ExceptionCrashHandler() {
23 
24     }
25 
26     public void init(Context context) {
27         /**
28         * 官方解释
29         * Set the handler invoked when this thread abruptly terminates
30         * due to an uncaught exception.
31          **/
32         Thread.currentThread().setUncaughtExceptionHandler(this);
33         // 获取系统默认的UncaughtException处理器
34         mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
35         this.mContext = context;
36     }
37 
38     @Override
39     public void uncaughtException(Thread t, Throwable ex) {
40         Log.e(TAG, "到拦截闪退信息");
41     }
42 
43 }
复制代码

在Application的onCreate()中配置一下,然后在任何一个地方写一个异常试一试:

复制代码
public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ExceptionCrashHandler.getInstance().init(this);
    }
}
复制代码

2.2 收集闪退信息

  这样每次崩溃的时候都会进入uncaughtException(),这个时候我们只需要收集信息写入本地文件就好了,收集的信息肯定需要包含好几个部分:当前崩溃信息,当前应用的版本信息,当前手机的信息,有的时候我们还需要其他部分,这里大概就只收集这三部分。为什么收集收集手机信息呢?因为有的时候是由于某些特定手机引起的Bug,若怪罪下来的话我们要甩锅给他。

复制代码
  1  @Override
  2     public void uncaughtException(Thread t, Throwable ex) {
  3         Log.e(TAG, "捕捉到了异常");
  4         // 1. 获取信息
  5         // 1.1 崩溃信息
  6         // 1.2 手机信息
  7         // 1.3 版本信息
  8         // 2.写入文件
  9         String crashFileName = saveInfoToSD(ex);
 10 
 11         Log.e(TAG, "fileName --> " + crashFileName);
 12 
 13         // 3. 缓存崩溃日志文件
 14         cacheCrashFile(crashFileName);
 15         // 系统默认处理
 16         mDefaultHandler.uncaughtException(t, ex);
 17     }
 18 
 19     /**
 20      * 缓存崩溃日志文件
 21      *
 22      * @param fileName
 23      */
 24     private void cacheCrashFile(String fileName) {
 25         SharedPreferences sp = mContext.getSharedPreferences("crash", Context.MODE_PRIVATE);
 26         sp.edit().putString("CRASH_FILE_NAME", fileName).commit();
 27     }
 28 
 29 
 30     /**
 31      * 获取崩溃文件名称
 32      *
 33      * @return
 34      */
 35     public File getCrashFile() {
 36         String crashFileName = mContext.getSharedPreferences("crash",
 37                 Context.MODE_PRIVATE).getString("CRASH_FILE_NAME", "");
 38         return new File(crashFileName);
 39     }
 40 
 41     /**
 42      * 保存获取的 软件信息,设备信息和出错信息保存在SDcard中
 43      *
 44      * @param ex
 45      * @return
 46      */
 47     private String saveInfoToSD(Throwable ex) {
 48         String fileName = null;
 49         StringBuffer sb = new StringBuffer();
 50 
 51         for (Map.Entry<String, String> entry : obtainSimpleInfo(mContext)
 52                 .entrySet()) {
 53             String key = entry.getKey();
 54             String value = entry.getValue();
 55             sb.append(key).append(" = ").append(value).append("\n");
 56         }
 57 
 58         sb.append(obtainExceptionInfo(ex));
 59 
 60         if (Environment.getExternalStorageState().equals(
 61                 Environment.MEDIA_MOUNTED)) {
 62             File dir = new File(mContext.getFilesDir() + File.separator + "crash"
 63                     + File.separator);
 64 
 65             // 先删除之前的异常信息
 66             if (dir.exists()) {
 67                 deleteDir(dir);
 68             }
 69 
 70             // 再从新创建文件夹
 71             if (!dir.exists()) {
 72                 dir.mkdir();
 73             }
 74             try {
 75                 fileName = dir.toString()
 76                         + File.separator
 77                         + getAssignTime("yyyy_MM_dd_HH_mm") + ".txt";
 78                 FileOutputStream fos = new FileOutputStream(fileName);
 79                 fos.write(sb.toString().getBytes());
 80                 fos.flush();
 81                 fos.close();
 82             } catch (Exception e) {
 83                 e.printStackTrace();
 84             }
 85         }
 86         return fileName;
 87     }
 88 
 89     /**
 90     * 返回当前日期根据格式
 91     **/
 92     private String getAssignTime(String dateFormatStr) {
 93         DateFormat dataFormat = new SimpleDateFormat(dateFormatStr);
 94         long currentTime = System.currentTimeMillis();
 95         return dataFormat.format(currentTime);
 96     }
 97 
 98 
 99     /**
100      * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
101      *
102      * @return
103      */
104     private HashMap<String, String> obtainSimpleInfo(Context context) {
105         HashMap<String, String> map = new HashMap<>();
106         PackageManager mPackageManager = context.getPackageManager();
107         PackageInfo mPackageInfo = null;
108         try {
109             mPackageInfo = mPackageManager.getPackageInfo(
110                     context.getPackageName(), PackageManager.GET_ACTIVITIES);
111         } catch (PackageManager.NameNotFoundException e) {
112             e.printStackTrace();
113         }
114         map.put("versionName", mPackageInfo.versionName);
115         map.put("versionCode", "" + mPackageInfo.versionCode);
116         map.put("MODEL", "" + Build.MODEL);
117         map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
118         map.put("PRODUCT", "" + Build.PRODUCT);
119         map.put("MOBLE_INFO", getMobileInfo());
120         return map;
121     }
122 
123 
124     /**
125      * Cell phone information
126      *
127      * @return
128      */
129     public static String getMobileInfo() {
130         StringBuffer sb = new StringBuffer();
131         try {
132             Field[] fields = Build.class.getDeclaredFields();
133             for (Field field : fields) {
134                 field.setAccessible(true);
135                 String name = field.getName();
136                 String value = field.get(null).toString();
137                 sb.append(name + "=" + value);
138                 sb.append("\n");
139             }
140         } catch (Exception e) {
141             e.printStackTrace();
142         }
143         return sb.toString();
144     }
145 
146 
147     /**
148      * 获取系统未捕捉的错误信息
149      *
150      * @param throwable
151      * @return
152      */
153     private String obtainExceptionInfo(Throwable throwable) {
154         StringWriter stringWriter = new StringWriter();
155         PrintWriter printWriter = new PrintWriter(stringWriter);
156         throwable.printStackTrace(printWriter);
157         printWriter.close();
158         return stringWriter.toString();
159     }
160 
161 
162     /**
163      * 递归删除目录下的所有文件及子目录下所有文件
164      *
165      * @param dir 将要删除的文件目录
166      * @return boolean Returns "true" if all deletions were successful. If a
167      * deletion fails, the method stops attempting to delete and returns
168      * "false".
169      */
170     private boolean deleteDir(File dir) {
171         if (dir.isDirectory()) {
172             String[] children = dir.list();
173             // 递归删除目录中的子目录下
174             for (int i = 0; i < children.length; i++) {
175                 boolean success = deleteDir(new File(dir, children[i]));
176                 if (!success) {
177                     return false;
178                 }
179             }
180         }
181         // 目录此时为空,可以删除
182         return true;
183     }
复制代码

保存的路径最好不要在用户的外部存储卡中,因为6.0的时候如果访问外部存储卡需要动态的申请权限,那这个时候信息是获取到了但是GG。都蹦了还拖着我不放,还需要申请权限,纳尼???

2.2 上传闪退信息

每次启动应用的时候就获取上次闪退的信息日志,然后上传到服务器。

复制代码
public class MainActivity extends BaseActivity {

    @Override
    protected void initData() {
        // 获取上次的崩溃信息
        File crashFile = ExceptionCrashHandler.getInstance().getCrashFile();
        // 上传到服务器,后面再说.......
    }

    @Override
    protected void initView() {

    }

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void initTitle() {

    }
}
复制代码

 

 

http://www.cnblogs.com/ganchuanpu/p/8196771.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值