Android Crash抓取处理

一、Android Crash说明

  • 程序因未捕获的异常而突然终止, 系统会调用处理程序的接口UncaughtExceptionHandler;

  • 处理未被程序正常捕获的异常,只需实现这个接口里的UncaughtExceptionHandler方法,UncaughtExceptionHandler方法回传了 Thread 和 Throwable 两个参数。

二、实现思路

  • 首先收集产生崩溃的手机信息,因为Android的样机种类繁多,很可能某些特定机型下会产生莫名的bug;

  • 将手机的信息和崩溃信息写入文件系统中。这样方便后续处理;

  • 崩溃的应用需要可以自动重启。重启的页面设置成反馈页面,询问 用户是否需要上传崩溃报告;

  • 用户同意后,即将写入的崩溃信息文件发送到自己的服务器。

三、代码展示

  • CrashApplication.java

      import android.app.Application;
      import android.os.Handler;
      import android.util.Log;
    
      public class CrashApplication extends Application{
          /** TAG */
          public static final String TAG = "CrashApplication";
          @Override
          public void onCreate() {
              super.onCreate();
              CrashHandler.getInstance().init(this);
              Log.v(TAG, "application created");
          }
      }   
    
  • CrashHandler.java

      import java.io.BufferedWriter;
      import java.io.File;
      import java.io.FileWriter;
      import java.io.IOException;
      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.app.AlarmManager;
      import android.app.PendingIntent;
      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.AsyncTask;
      import android.os.Build;
      import android.os.Environment;
      import android.util.Log;
    
      public class CrashHandler implements UncaughtExceptionHandler{
    
          /** TAG */
          private static final String TAG = "CrashHandler";
    
          /**
           *  uploadUrl 
           *  服务器的地址,根据自己的情况进行更改
          **/
          private static final String uploadUrl = "http://3.saymagic.sinaapp.com/ReceiveCrash.php";
    
          /**
           * localFileUrl
           * 本地log文件的存放地址
           **/
          private static String localFileUrl = "";
          
          /** mDefaultHandler */
          private Thread.UncaughtExceptionHandler defaultHandler;
    
          /** instance */
          private static CrashHandler instance = new CrashHandler();
    
          /** infos */
          private Map<String, String> infos = new HashMap<String, String>();
    
          /** formatter */
          private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
          /** context*/
          private CrashApplication context;
          private CrashHandler() {}
    
          public static CrashHandler getInstance() {
              if (instance == null) {
                  instance = new CrashHandler();
              }
              return instance;
          }
    
          /**
           * @param ctx
           * 初始化,此处最好在Application的OnCreate方法里来进行调用
           */
          public void init(CrashApplication ctx) {
              this.context = ctx;
              defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
              Thread.setDefaultUncaughtExceptionHandler(this);
          }
    
          /**
           * uncaughtException
           * 在这里处理为捕获的Exception
           */
          @Override
          public void uncaughtException(Thread thread, Throwable throwable) {
              handleException(throwable);
              defaultHandler.uncaughtException(thread, throwable);
          }
          private boolean handleException(Throwable ex) {
              if (ex == null) {
                  return false;
              }
              Log.d("TAG", "收到崩溃");
              collectDeviceInfo(context);
              writeCrashInfoToFile(ex);
              restart();
              return true;
          }
    
          /**
           * 
           * @param ctx
           * 手机设备相关信息
           */
          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 == null ? "null"
                              : pi.versionName;
                      String versionCode = pi.versionCode + "";
                      infos.put("versionName", versionName);
                      infos.put("versionCode", versionCode);
                      infos.put("crashTime", formatter.format(new Date()));
                  }
              } 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());
                      Log.d(TAG, field.getName() + " : " + field.get(null));
                  } catch (Exception e) {
                      Log.e(TAG, "an error occured when collect crash info", e);
                  }
              }
          }
    
          /**
           * 
           * @param ex
           * 将崩溃写入文件系统
           */
          private void writeCrashInfoToFile(Throwable ex) {
              StringBuffer sb = new StringBuffer();
              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.close();
              String result = writer.toString();
              sb.append(result);
    
              //这里把刚才异常堆栈信息写入SD卡的Log日志里面
              if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                  String sdcardPath = Environment.getExternalStorageDirectory().getPath();
                  String filePath = sdcardPath + "/cym/crash/";
                  localFileUrl = writeLog(sb.toString(), filePath);
              }
          }
    
          /**
           * 
           * @param log
           * @param name
           * @return 返回写入的文件路径
           * 写入Log信息的方法,写入到SD卡里面
           */
          private String writeLog(String log, String name) {
              CharSequence timestamp = new Date().toString().replace(" ", "");
              timestamp  = "crash";
              String filename = name + timestamp + ".log";
    
              File file = new File(filename);
              if(!file.getParentFile().exists()){
                  file.getParentFile().mkdirs();
              }
              try {
                  Log.d("TAG", "写入到SD卡里面");
                  //          FileOutputStream stream = new FileOutputStream(new File(filename));
                  //          OutputStreamWriter output = new OutputStreamWriter(stream);
                  file.createNewFile();
                  FileWriter fw=new FileWriter(file,true);   
                  BufferedWriter bw = new BufferedWriter(fw);
                  //写入相关Log到文件
                  bw.write(log);
                  bw.newLine();
                  bw.close();
                  fw.close();
                  return filename;
              } catch (IOException e) {
                  Log.e(TAG, "an error occured while writing file...", e);
                  e.printStackTrace();
                  return null;
              }
          }
    
          private void restart(){
               try{    
                   Thread.sleep(2000);    
               }catch (InterruptedException e){    
                   Log.e(TAG, "error : ", e);    
               }     
              Intent intent = new Intent(context.getApplicationContext(), SendCrashActivity.class);  
              PendingIntent restartIntent = PendingIntent.getActivity(    
                       context.getApplicationContext(), 0, intent,    
                       Intent.FLAG_ACTIVITY_NEW_TASK);                                                 
               //退出程序                                          
               AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);    
               mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,    
                       restartIntent); // 1秒钟后重启应用   
              }
    
          }
    
  • MainActivity.java

      import android.app.Activity;
      import android.os.Bundle;
      import android.view.Menu;
      import android.view.View;
      import android.widget.Toast;
    
      public class MainActivity extends Activity {
    
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
    
          /**
           * 点击按钮后故意产生崩溃
           * @param view
           */
          public void generateCrash(View view){
              int a = 2/0;
          }
      }
    
  • SendCrashActivity.java

      import java.io.File;
    
      import android.app.Activity;
      import android.os.AsyncTask;
      import android.os.Bundle;
      import android.os.Environment;
      import android.util.Log;
      import android.view.Menu;
      import android.view.View;
      import android.widget.Toast;
    
      /**
       * 发送crash的activity。该activity是在崩溃后自动重启的。
       */
      public class SendCrashActivity extends Activity {
    
          private static final String uploadUrl = "http://3.saymagic.sinaapp.com/ReceiveCrash.php";
    
          /**
           * localFileUrl
           * 本地log文件的存放地址
           */
          private static String localFileUrl = "";
          
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_send_crash);
              //这里把刚才异常堆栈信息写入SD卡的Log日志里面
              if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                  String sdcardPath = Environment.getExternalStorageDirectory().getPath();
                  localFileUrl = sdcardPath + "/cym/crash/crash.log";
              }
          }
    
          public void sendCrash(View view){
              new SendCrashLog().execute("");
          }
          @Override
          public boolean onCreateOptionsMenu(Menu menu) {
              // Inflate the menu; this adds items to the action bar if it is present.
              getMenuInflater().inflate(R.menu.send_crash, menu);
              return true;
          }
          /**
           * 向服务器发送崩溃信息
           */
          public class SendCrashLog extends AsyncTask<String, String, Boolean> {
              public SendCrashLog() {  }
    
              @Override
              protected Boolean doInBackground(String... params) {
                  Log.d("TAG", "向服务器发送崩溃信息");
                  UploadUtil.uploadFile(new File(localFileUrl), uploadUrl);
                  return null;
              }
    
              @Override
              protected void onPostExecute(Boolean result) {
                  Toast.makeText(getApplicationContext(), "成功将崩溃信息发送到服务器,感谢您的反馈", 1000).show();
                  Log.d("TAG", "发送完成");   
              }
          }
      }
    
  • UploadUtil.java

      import java.io.DataOutputStream;
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.net.HttpURLConnection;
      import java.net.MalformedURLException;
      import java.net.URL;
      import java.util.UUID;
    
      import android.util.Log;
    
      public class UploadUtil {
    
          private static final String TAG = "UPLOADUTIL";
          private static final int TIME_OUT = 10*1000;
          private static final String CHARSET = "utf-8";
    
          public static String uploadFile(File file,String requestUrl){
              String result = null;
              String  BOUNDARY =  UUID.randomUUID().toString();  //边界标识   随机生成
              String PREFIX = "--" ;
              String LINE_END = "\r\n";
              String CONTENT_TYPE = "multipart/form-data";   //内容类型
              try{
                  URL url = new URL(requestUrl);
                  HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                  conn.setReadTimeout(TIME_OUT);
                  conn.setConnectTimeout(TIME_OUT);
                  conn.setDoInput(true);  //允许输入流
                  conn.setDoOutput(true); //允许输出流
                  conn.setUseCaches(false);  //不允许使用缓存
                  conn.setRequestMethod("POST");  //请求方式
                  conn.setRequestProperty("Charset", CHARSET);  //设置编码
                  conn.setRequestProperty("connection", "keep-alive");
                  conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);
    
                  if(file!=null)
                  {
                      /**
                       * 当文件不为空,把文件包装并且上传
                       */
                      DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
                      StringBuffer sb = new StringBuffer();
                      sb.append(PREFIX);
                      sb.append(BOUNDARY);
                      sb.append(LINE_END);
                      /**
                       * 这里重点注意:
                       * name里面的值为服务器端需要key   只有这个key 才可以得到对应的文件
                       * filename是文件的名字,包含后缀名的   比如:abc.png
                       */
    
                      sb.append("Content-Disposition: form-data; name=\"uploadcrash\"; filename=\""+file.getName()+"\""+LINE_END);
                      sb.append("Content-Type: application/octet-stream; charset="+CHARSET+LINE_END);
                      sb.append(LINE_END);
                      dos.write(sb.toString().getBytes());
                      InputStream is = new FileInputStream(file);
                      byte[] bytes = new byte[1024];
                      int len = 0;
                      while((len=is.read(bytes))!=-1)
                      {
                          dos.write(bytes, 0, len);
                      }
                      is.close();
                      dos.write(LINE_END.getBytes());
                      byte[] end_data = (PREFIX+BOUNDARY+PREFIX+LINE_END).getBytes();
                      dos.write(end_data);
                      dos.flush();
                      /**
                       * 获取响应码  200=成功
                       * 当响应成功,获取响应的流
                       */
                      int res = conn.getResponseCode();
                      Log.e(TAG, "response code:"+res);
                      //              if(res==200)
                      //              {
                      Log.e(TAG, "request success");
                      InputStream input =  conn.getInputStream();
                      StringBuffer sb1= new StringBuffer();
                      int ss ;
                      while((ss=input.read())!=-1)
                      {
                          sb1.append((char)ss);
                      }
                      result = sb1.toString();
                      Log.e(TAG, "result : "+ result);
                      //              }
                      //              else{
                      //                  Log.e(TAG, "request error");
                      //              }
                  }
              }catch (MalformedURLException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
    
              return result;
          }
      }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,042评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 2017年5月17日 Kylin_Wu 标注(★☆)为考纲明确给出考点(必考) 常见手机系统(★☆) And...
    Azur_wxj阅读 1,784评论 0 10
  • 亲爱的爸爸妈妈: 你们好! 很感谢你们无微不至的照顾,现在我已经11个月啦!这十一个月,我变化很大,从最初的六斤半...
    日落半林阅读 294评论 1 3