Android Crash抓取处理

http://www.jianshu.com/p/0e9bf00512ec

Android Crash抓取处理

144
作者 Mur
2016.03.21 09:42 字数 1827 阅读 349 评论 0
一、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;
          }
      }
IT生涯

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

赞赏支持
智慧如你,不想 发表一点想法咩~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值