Android Crash抓取处理

时间:2021-01-20 15:28:05

一、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;
      }
  }