Android Crash抓取處理

一拗踢、Android Crash說明

  • 程序因未捕獲的異常而突然終止, 系統(tǒng)會調(diào)用處理程序的接口UncaughtExceptionHandler;

  • 處理未被程序正常捕獲的異常烁焙,只需實現(xiàn)這個接口里的UncaughtExceptionHandler方法副编,UncaughtExceptionHandler方法回傳了 Thread 和 Throwable 兩個參數(shù)。

二经瓷、實現(xiàn)思路

  • 首先收集產(chǎn)生崩潰的手機信息汤善,因為Android的樣機種類繁多危号,很可能某些特定機型下會產(chǎn)生莫名的bug;

  • 將手機的信息和崩潰信息寫入文件系統(tǒng)中牧愁。這樣方便后續(xù)處理;

  • 崩潰的應用需要可以自動重啟。重啟的頁面設置成反饋頁面外莲,詢問 用戶是否需要上傳崩潰報告;

  • 用戶同意后猪半,即將寫入的崩潰信息文件發(fā)送到自己的服務器。

三苍狰、代碼展示

  • 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 
           *  服務器的地址办龄,根據(jù)自己的情況進行更改
          **/
          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方法里來進行調(diào)用
           */
          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
           * 將崩潰寫入文件系統(tǒng)
           */
          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);
          }
    
          /**
           * 點擊按鈕后故意產(chǎn)生崩潰
           * @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;
    
      /**
       * 發(fā)送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;
          }
          /**
           * 向服務器發(fā)送崩潰信息
           */
          public class SendCrashLog extends AsyncTask<String, String, Boolean> {
              public SendCrashLog() {  }
    
              @Override
              protected Boolean doInBackground(String... params) {
                  Log.d("TAG", "向服務器發(fā)送崩潰信息");
                  UploadUtil.uploadFile(new File(localFileUrl), uploadUrl);
                  return null;
              }
    
              @Override
              protected void onPostExecute(Boolean result) {
                  Toast.makeText(getApplicationContext(), "成功將崩潰信息發(fā)送到服務器翔忽,感謝您的反饋", 1000).show();
                  Log.d("TAG", "發(fā)送完成");   
              }
          }
      }
    
  • 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";   //內(nèi)容類型
              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;
          }
      }
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奏赘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子材失,更是在濱河造成了極大的恐慌,老刑警劉巖硫豆,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龙巨,死亡現(xiàn)場離奇詭異,居然都是意外死亡熊响,警方通過查閱死者的電腦和手機旨别,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汗茄,“玉大人秸弛,你說我怎么就攤上這事。” “怎么了递览?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵叼屠,是天一觀的道長。 經(jīng)常有香客問我绞铃,道長环鲤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任憎兽,我火速辦了婚禮冷离,結果婚禮上,老公的妹妹穿的比我還像新娘纯命。我一直安慰自己西剥,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布亿汞。 她就那樣靜靜地躺著瞭空,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疗我。 梳的紋絲不亂的頭發(fā)上咆畏,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音吴裤,去河邊找鬼旧找。 笑死,一個胖子當著我的面吹牛麦牺,可吹牛的內(nèi)容都是我干的钮蛛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剖膳,長吁一口氣:“原來是場噩夢啊……” “哼魏颓!你這毒婦竟也來了?” 一聲冷哼從身側響起吱晒,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤甸饱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仑濒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叹话,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年躏精,在試婚紗的時候發(fā)現(xiàn)自己被綠了渣刷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡矗烛,死狀恐怖辅柴,靈堂內(nèi)的尸體忽然破棺而出箩溃,到底是詐尸還是另有隱情,我是刑警寧澤碌嘀,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布涣旨,位于F島的核電站,受9級特大地震影響股冗,放射性物質(zhì)發(fā)生泄漏霹陡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一止状、第九天 我趴在偏房一處隱蔽的房頂上張望烹棉。 院中可真熱鬧,春花似錦怯疤、人聲如沸浆洗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伏社。三九已至,卻和暖如春塔淤,著一層夾襖步出監(jiān)牢的瞬間摘昌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工高蜂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聪黎,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓妨马,卻偏偏與公主長得像挺举,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烘跺,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)脂崔,斷路器滤淳,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,075評論 25 707
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法砌左,內(nèi)部類的語法脖咐,繼承相關的語法,異常的語法汇歹,線程的語...
    子非魚_t_閱讀 31,623評論 18 399
  • 2017年5月17日 Kylin_Wu 標注(★☆)為考綱明確給出考點(必考) 常見手機系統(tǒng)(★☆) And...
    Azur_wxj閱讀 1,810評論 0 10
  • 親愛的爸爸媽媽: 你們好屁擅! 很感謝你們無微不至的照顧,現(xiàn)在我已經(jīng)11個月啦产弹!這十一個月派歌,我變化很大,從最初的六斤半...
    日落半林閱讀 307評論 1 3