Android热修复的 – 收集崩溃信息上传服务器。Android热修复的 – 收集崩溃信息上传服务器。

1.概述

  大致的流程便是于用户崩溃的时,我们获得崩溃信息、应用时的信息和手机信息,然后将她保存及手机内存卡,再找我就一直搜索出来看。后来衍生到上线后某些奇葩机型会发局部问题,所以只好上传播服务器,后来发现还是可以配合热修复一步一步如此神奇,接下去我们来娱乐同样玩,如何才会将用户的夭折信息及传来服务器。大家也得错过探寻腾讯他产生成的:https://bugly.qq.com/v2/index
友盟也来备的:http://www.umeng.com/
实现的规律都类似。

1.概述


始发想征集崩溃信息是因测试的兄弟老是说崩了,但是同过来就是起来拍首说
我*怎好了?所以后来上网查了不少音,找到了同等种方式。大致的流程便是在用户崩溃的时,我们得到崩溃信息、应用时之信及手机信息,然后拿它们保存及手机内存卡,再寻找我虽直接搜索出来看。后来衍生到上线后某些奇葩机型会有局部问题,所以只好上传服务器,后来察觉还是可以配合热修复一步一步如此神奇,接下去我们来娱乐同样玩,如何才会拿用户的夭折信息及传播服务器。大家吧得错过追寻腾讯他生成的:https://bugly.qq.com/v2/index
友盟也起备的:http://www.umeng.com/
实现的规律都接近。

视频讲解:https://pan.baidu.com/s/1nwlqeA9

连锁文章:

2017Android进阶的路以及公同行
  
Android热修复的 –
收集崩溃信息上传至服务器

Android热修复的 –
阿里开源之热补丁

Android热修复的 –
打补丁原来如此简单

图片 1

GIF.gif

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() {

    }
}

 

2.实现


2.1 拦截闪退信息
  
  如何错过采访我们的闪退信息?我们用认识一下这个类Thread.UncaughtExceptionHandler,一言不和就看源码,这个可以无扣,且看我是何许勾勒的。

/**
 * Created by Darren on 2017/2/8.
 * Email: 240336124@qq.com
 * Description: 拦截应用的闪退信息
 */
public class ExceptionCrashHandler implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "ExceptionCrashHandler";
    // 单例设计模式
    private static ExceptionCrashHandler mInstance;
    // 留下原来的,便于开发的时候调试
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    // 上下文  获取版本信息和手机信息
    private Context mContext;

    public static ExceptionCrashHandler getInstance() {
        if (mInstance == null) {
            synchronized (ExceptionCrashHandler.class) {
                if (mInstance == null) {
                    mInstance = new ExceptionCrashHandler();
                }
            }
        }
        return mInstance;
    }

    private ExceptionCrashHandler() {

    }

    public void init(Context context) {
        /**
        * 官方解释
        * Set the handler invoked when this thread abruptly terminates
        * due to an uncaught exception.
         **/
        Thread.currentThread().setUncaughtExceptionHandler(this);
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        this.mContext = context;
    }

    @Override
    public void uncaughtException(Thread t, Throwable ex) {
        Log.e(TAG, "到拦截闪退信息");
    }

}

于Application的onCreate()中布置一下,然后以其他一个地方写一个格外试一试行:

/**
 * Created by Darren on 2017/2/8.
 * Email: 240336124@qq.com
 * Description: BaseApplication
 */
public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ExceptionCrashHandler.getInstance().init(this);
    }
}

2.2 收集闪退信息

这样每次崩溃的时段还见面进uncaughtException(),这个时段咱们仅仅待募信息写副当地文件就吓了,收集之音讯一定需要包含好几单部分:当前倒信息,当前采取之版本信息,当前手机的信,有的上咱们尚需要另部分,这里大约就特收集这三有。为什么收集收集手机信息呢?因为有时候是由于一些特定手机招的Bug,若怪罪下来的口舌我们要甩锅给他。

    @Override
    public void uncaughtException(Thread t, Throwable ex) {
        Log.e(TAG, "捕捉到了异常");
        // 1. 获取信息
        // 1.1 崩溃信息
        // 1.2 手机信息
        // 1.3 版本信息
        // 2.写入文件
        String crashFileName = saveInfoToSD(ex);

        Log.e(TAG, "fileName --> " + crashFileName);

        // 3. 缓存崩溃日志文件
        cacheCrashFile(crashFileName);
        // 系统默认处理
        mDefaultHandler.uncaughtException(t, ex);
    }

    /**
     * 缓存崩溃日志文件
     *
     * @param fileName
     */
    private void cacheCrashFile(String fileName) {
        SharedPreferences sp = mContext.getSharedPreferences("crash", Context.MODE_PRIVATE);
        sp.edit().putString("CRASH_FILE_NAME", fileName).commit();
    }


    /**
     * 获取崩溃文件名称
     *
     * @return
     */
    public File getCrashFile() {
        String crashFileName = mContext.getSharedPreferences("crash",
                Context.MODE_PRIVATE).getString("CRASH_FILE_NAME", "");
        return new File(crashFileName);
    }

    /**
     * 保存获取的 软件信息,设备信息和出错信息保存在SDcard中
     *
     * @param ex
     * @return
     */
    private String saveInfoToSD(Throwable ex) {
        String fileName = null;
        StringBuffer sb = new StringBuffer();

        for (Map.Entry<String, String> entry : obtainSimpleInfo(mContext)
                .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(mContext.getFilesDir() + File.separator + "crash"
                    + File.separator);

            // 先删除之前的异常信息
            if (dir.exists()) {
                deleteDir(dir);
            }

            // 再从新创建文件夹
            if (!dir.exists()) {
                dir.mkdir();
            }
            try {
                fileName = dir.toString()
                        + File.separator
                        + getAssignTime("yyyy_MM_dd_HH_mm") + ".txt";
                FileOutputStream fos = new FileOutputStream(fileName);
                fos.write(sb.toString().getBytes());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return fileName;
    }

    /**
    * 返回当前日期根据格式
    **/
    private String getAssignTime(String dateFormatStr) {
        DateFormat dataFormat = new SimpleDateFormat(dateFormatStr);
        long currentTime = System.currentTimeMillis();
        return dataFormat.format(currentTime);
    }


    /**
     * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
     *
     * @return
     */
    private HashMap<String, String> obtainSimpleInfo(Context context) {
        HashMap<String, String> map = new HashMap<>();
        PackageManager mPackageManager = context.getPackageManager();
        PackageInfo mPackageInfo = null;
        try {
            mPackageInfo = mPackageManager.getPackageInfo(
                    context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.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);
        map.put("MOBLE_INFO", getMobileInfo());
        return map;
    }


    /**
     * Cell phone information
     *
     * @return
     */
    public static String getMobileInfo() {
        StringBuffer sb = new StringBuffer();
        try {
            Field[] fields = Build.class.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String name = field.getName();
                String value = field.get(null).toString();
                sb.append(name + "=" + value);
                sb.append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }


    /**
     * 获取系统未捕捉的错误信息
     *
     * @param throwable
     * @return
     */
    private String obtainExceptionInfo(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        throwable.printStackTrace(printWriter);
        printWriter.close();
        return stringWriter.toString();
    }


    /**
     * 递归删除目录下的所有文件及子目录下所有文件
     *
     * @param dir 将要删除的文件目录
     * @return boolean Returns "true" if all deletions were successful. If a
     * deletion fails, the method stops attempting to delete and returns
     * "false".
     */
    private boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            // 递归删除目录中的子目录下
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空,可以删除
        return true;
    }

保留之路线最好不用在用户的外表存储卡中,因为6.0之时节要访问外部存储卡需要动态的提请权限,那这个时信息是获取到了但GG。都跳了尚拖在我莫放,还欲申请权限,纳尼???

2.2 上传闪退信息

历次启动以之时节便拿走上次闪退的音信日志,然后上传来服务器。上传就先行不齐污染了,等内涵段子的网架构和效应都结束后我们还来达到传,然后装进但是未可知上丝,要不然要背官司的。

/**
 * Created by Darren on 2017/2/8.
 * Email: 240336124@qq.com
 * Description: 主页面MainActivity
 */
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() {

    }
}

视频讲解:https://pan.baidu.com/s/1nwlqeA9

系文章:

2017Android进阶的路与您同行
  
Android热修复的 –
收集崩溃信息及传至服务器

Android热修复的 –
阿里开源的热补丁

Android热修复的 –
打补丁原来如此简单

相关文章