一.概述
发布过产品的android程序员都会知道,在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,尤其是某些不容易复现的bug,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序,并且定时删除这些日志文件。参考网上相关文章,自己做了部分改进和完善。
二.项目简介
项目结构如图:
MainActivity内的代码如下:
- package com.example.mycrashtest;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.Menu;
- import android.widget.TextView;
-
- public class MainActivity extends Activity {
-
- private TextView tv;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- tv.setText("我没初始化");
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
-
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
-
- }
在这里,我没对TextView进行初始化,直接给其进行设置文本,会造成运行时异常,没有对此异常进行处理,系统会弹出停止运行并强制关闭。
三.Appcation与Thread.UncaughtExceptionHandler
Appcation:Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如果需要创建自己 的Application,也很简单创建一个类继承 Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的 Application的名字定入即可)。
android系统会为每个程序运行时创建一个Application类的对象且仅创建一个,所以Application可以说是单例 (singleton)模式的一个类.且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局 的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享 等,数据缓存等操作。
Thread.UncaughtExceptionHandler:当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用
Thread.getUncaughtExceptionHandler()
查询该线程以获得其
UncaughtExceptionHandler 的线程,并调用处理程序的
uncaughtException 方法,将线程和异常作为参数传递。如果某一线程没有明确设置其
UncaughtExceptionHandler,则将它的
ThreadGroup 对象作为其
UncaughtExceptionHandler。如果
ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给
默认的未捕获异常处理程序。
我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
四.代码实现
- <pre name="code" class="java">package com.example.mycrashtest;
-
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.FilenameFilter;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import java.io.Writer;
- import java.lang.Thread.UncaughtExceptionHandler;
- import java.lang.reflect.Field;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
-
- import android.annotation.SuppressLint;
- import android.content.Context;
- import android.content.Intent;
- 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.os.SystemClock;
- import android.util.Log;
- import android.widget.Toast;
-
-
-
-
-
-
-
- @SuppressLint("SimpleDateFormat")
- public class CrashHandler implements UncaughtExceptionHandler {
-
- public static String TAG = "MyCrash";
-
- private Thread.UncaughtExceptionHandler mDefaultHandler;
-
- private static CrashHandler instance = new CrashHandler();
- private Context mContext;
-
-
- private Map<String, String> infos = new HashMap<String, String>();
-
-
- private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
-
-
- private CrashHandler() {
- }
-
-
- public static CrashHandler getInstance() {
- return instance;
- }
-
-
-
-
-
-
- public void init(Context context) {
- mContext = context;
-
- mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
-
- Thread.setDefaultUncaughtExceptionHandler(this);
- autoClear(5);
- }
-
-
-
-
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
- if (!handleException(ex) && mDefaultHandler != null) {
-
- mDefaultHandler.uncaughtException(thread, ex);
- } else {
- SystemClock.sleep(3000);
-
- android.os.Process.killProcess(android.os.Process.myPid());
- System.exit(1);
- }
- }
-
-
-
-
-
-
-
- private boolean handleException(Throwable ex) {
- if (ex == null)
- return false;
-
- try {
-
- new Thread() {
-
- @Override
- public void run() {
- Looper.prepare();
- Toast.makeText(mContext, "很抱歉,程序出现异常,即将重启.",
- Toast.LENGTH_LONG).show();
- Looper.loop();
- }
- }.start();
-
- collectDeviceInfo(mContext);
-
- saveCrashInfoFile(ex);
- SystemClock.sleep(3000);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- return true;
- }
-
-
-
-
-
-
- public void collectDeviceInfo(Context ctx) {
- try {
- PackageManager pm = ctx.getPackageManager();
- PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
- PackageManager.GET_ACTIVITIES);
- if (pi != null) {
- String versionName = pi.versionName + "";
- String versionCode = pi.versionCode + "";
- infos.put("versionName", versionName);
- infos.put("versionCode", versionCode);
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "an error occured when collect package info", e);
- }
- Field[] fields = Build.class.getDeclaredFields();
- for (Field field : fields) {
- try {
- field.setAccessible(true);
- infos.put(field.getName(), field.get(null).toString());
- } catch (Exception e) {
- Log.e(TAG, "an error occured when collect crash info", e);
- }
- }
- }
-
-
-
-
-
-
-
- private String saveCrashInfoFile(Throwable ex) throws Exception {
- StringBuffer sb = new StringBuffer();
- try {
- SimpleDateFormat sDateFormat = new SimpleDateFormat(
- "yyyy-MM-dd HH:mm:ss");
- String date = sDateFormat.format(new java.util.Date());
- sb.append("\r\n" + date + "\n");
- for (Map.Entry<String, String> entry : infos.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- sb.append(key + "=" + value + "\n");
- }
-
- Writer writer = new StringWriter();
- PrintWriter printWriter = new PrintWriter(writer);
- ex.printStackTrace(printWriter);
- Throwable cause = ex.getCause();
- while (cause != null) {
- cause.printStackTrace(printWriter);
- cause = cause.getCause();
- }
- printWriter.flush();
- printWriter.close();
- String result = writer.toString();
- sb.append(result);
-
- String fileName = writeFile(sb.toString());
- return fileName;
- } catch (Exception e) {
- Log.e(TAG, "an error occured while writing file...", e);
- sb.append("an error occured while writing file...\r\n");
- writeFile(sb.toString());
- }
- return null;
- }
-
- private String writeFile(String sb) throws Exception {
- String time = formatter.format(new Date());
- String fileName = "crash-" + time + ".log";
- if (FileUtil.hasSdcard()) {
- String path = getGlobalpath();
- File dir = new File(path);
- if (!dir.exists())
- dir.mkdirs();
-
- FileOutputStream fos = new FileOutputStream(path + fileName, true);
- fos.write(sb.getBytes());
- fos.flush();
- fos.close();
- }
- return fileName;
- }
-
- public static String getGlobalpath() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()
- + File.separator + "crash" + File.separator;
- }
-
- public static void setTag(String tag) {
- TAG = tag;
- }
-
-
-
-
-
- public void autoClear(final int autoClearDay) {
- FileUtil.delete(getGlobalpath(), new FilenameFilter() {
-
- @Override
- public boolean accept(File file, String filename) {
- String s = FileUtil.getFileNameWithoutExtension(filename);
- int day = autoClearDay < 0 ? autoClearDay : -1 * autoClearDay;
- String date = "crash-" + DateUtil.getOtherDay(day);
- return date.compareTo(s) >= 0;
- }
- });
-
- }
-
- }
需要在自己的Appcation类中初始化这个CrashHandler:
- package com.example.mycrashtest;
-
- import android.app.Application;
-
- public class MyAppcation extends Application{
-
- @Override
- public void onCreate() {
- super.onCreate();
- CrashHandler.getInstance().init(this);
- }
-
- }
在AndroidManifest.xml文件中需要修改为自己的Appcation,并且加入操作SDCARD权限(写日志):
四.运行结果
系统会自动在SDCARD下创建crash目录:
打开该文件:
至此,整个项目梳理完毕,关于将日志文件上传FTP,后面会另外讲述,部分知识本人也处于学习阶段,不足之处望大家谅解并提出,共同学习,谢谢!
项目下载:
点击打开链接