1. 程式人生 > >Android App崩潰上傳日誌到伺服器並且重啟!

Android App崩潰上傳日誌到伺服器並且重啟!

我們寫程式的時候都希望能寫出一個沒有任何Bug的程式,期望在任何情況下都不會發生程式崩潰。但沒有一個程式設計師能保證自己寫的程式絕對不會出現異常崩潰。特別是當你使用者數達到一定數量級後,你也更容易發現應用不同情況下的崩潰。

  對於還沒釋出的應用程式,我們可以通過測試、分析Log的方法來收集崩潰資訊。但對已經發布的程式,我們不可能讓使用者去檢視崩潰資訊然後再反饋給開發者。所以,設計一個對於小白使用者都可以輕鬆實現反饋的應用就顯得很重要了。我這裡結合我自己寫的一個Demo,來分析從崩潰開始到崩潰資訊反饋到我們伺服器,我們程式都需要做什麼。

當我們的程式因未捕獲的異常而突然終止時,系統會呼叫處理程式的介面UncaughtExceptionHandler

。如果我們想處理未被程式正常捕獲的異常,只需實現這個接口裡的uncaughtException方法,uncaughtException方法回傳了Thread 和 Throwable兩個引數。通過這兩個引數,我們來對異常進行我們需要的處理。

綜上,我對異常處理方式的思路是這樣的:

1.我們需要首先收集產生崩潰的手機資訊,因為Android的樣機種類繁多,很可能某些特定機型下會產生莫名的bug。
2.將手機的資訊和崩潰資訊寫入檔案系統中。這樣方便後續處理。

3.崩潰的應用需要可以自動重啟。重啟的頁面設定成反饋頁面,詢問 使用者是否需要上傳崩潰報告。

4.使用者同意後,即將2中寫入的崩潰資訊檔案傳送到自己的伺服器。

通過上面的步驟,我們就可以寫出大概的虛擬碼:

  1. handleException(){
  2. collectDeviceInfo(context);//手機手機資訊
  3. writeCrashInfoToFile(ex);//寫入崩潰檔案
  4. restart();//應用重啟
  5. }

最後,在重啟頁面通過AsyncTask將崩潰資訊上傳伺服器。

有了以上思路,我們一步一步的寫出每個偽函式的具體程式碼。

1.收集手機的資訊:

  1. /**
  2. *
  3. * @param ctx
  4. * 手機裝置相關資訊
  5. */
  6. publicvoid collectDeviceInfo(Context ctx){
  7. try{
  8. PackageManager
    pm = ctx.getPackageManager();
  9. PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
  10. PackageManager.GET_ACTIVITIES);
  11. if(pi !=null){
  12. String versionName = pi.versionName ==null?"null"
  13. : pi.versionName;
  14. String versionCode = pi.versionCode +"";
  15. infos.put("versionName", versionName);
  16. infos.put("versionCode", versionCode);
  17. infos.put("crashTime", formatter.format(newDate()));
  18. }
  19. }catch(NameNotFoundException e){
  20. Log.e(TAG,"an error occured when collect package info", e);
  21. }
  22. Field[] fields =Build.class.getDeclaredFields();
  23. for(Field field: fields){
  24. try{
  25. field.setAccessible(true);
  26. infos.put(field.getName(), field.get(null).toString());
  27. Log.d(TAG, field.getName()+" : "+ field.get(null));
  28. }catch(Exception e){
  29. Log.e(TAG,"an error occured when collect crash info", e);
  30. }
  31. }
  32. }

2.崩潰和手機資訊寫入檔案:

  1. /**
  2. *
  3. * @param ex
  4. * 將崩潰寫入檔案系統
  5. */
  6. privatevoid writeCrashInfoToFile(Throwable ex){
  7. StringBuffer sb =newStringBuffer();
  8. for(Map.Entry<String,String> entry: infos.entrySet()){
  9. String key = entry.getKey();
  10. String value = entry.getValue();
  11. sb.append(key +"="+ value +"\n");
  12. }
  13. Writer writer =newStringWriter();
  14. PrintWriter printWriter =newPrintWriter(writer);
  15. ex.printStackTrace(printWriter);
  16. Throwable cause = ex.getCause();
  17. while(cause !=null){
  18. cause.printStackTrace(printWriter);
  19. cause = cause.getCause();
  20. }
  21. printWriter.close();
  22. String result = writer.toString();
  23. sb.append(result);
  24. //這裡把剛才異常堆疊資訊寫入SD卡的Log日誌裡面
  25. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
  26. {
  27. String sdcardPath =Environment.getExternalStorageDirectory().getPath();
  28. String filePath = sdcardPath +"/cym/crash/";
  29. localFileUrl = writeLog(sb.toString(), filePath);
  30. }
  31. }
  32. /**
  33. *
  34. * @param log
  35. * @param name
  36. * @return 返回寫入的檔案路徑
  37. * 寫入Log資訊的方法,寫入到SD卡里面
  38. */
  39. privateString writeLog(String log,String name)
  40. {
  41. CharSequence timestamp =newDate().toString().replace(" ","");
  42. timestamp ="crash";
  43. String filename = name + timestamp +".log";
  44. File file =newFile(filename);
  45. if(!file.getParentFile().exists()){
  46. file.getParentFile().mkdirs();
  47. }
  48. try
  49. {
  50. Log.d("TAG","寫入到SD卡里面");
  51. // FileOutputStream stream = new FileOutputStream(new File(filename));
  52. // OutputStreamWriter output = new OutputStreamWriter(stream);
  53. file.createNewFile();
  54. FileWriter fw=newFileWriter(file,true);
  55. BufferedWriter bw =newBufferedWriter(fw);
  56. //寫入相關Log到檔案
  57. bw.write(log);
  58. bw.newLine();
  59. bw.close();
  60. fw.close();
  61. return filename;
  62. }
  63. catch(IOException e)
  64. {
  65. Log.e(TAG,"an error occured while writing file...", e);
  66. e.printStackTrace();
  67. returnnull;
  68. }
  69. }

3.重啟應用:

  1. 注:我嘗試過好多種應用重啟的方法,最終選擇採用PendingIntent的方式。
  2. privatevoid restart(){
  3. try{
  4. Thread.sleep(2000);
  5. }catch(InterruptedException e){
  6. Log.e(TAG,"error : ", e);
  7. }
  8. Intent intent =newIntent(context.getApplicationContext(),SendCrashActivity.class);
  9. PendingIntent restartIntent =PendingIntent.getActivity(
  10. context.getApplicationContext(),0, intent,
  11. Intent.FLAG_ACTIVITY_NEW_TASK);
  12. //退出程式
  13. AlarmManager mgr =(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
  14. mgr.set(AlarmManager.RTC,System.currentTimeMillis()+1000,
  15. restartIntent);// 1秒鐘後重啟應用
  16. }

4.上傳崩潰

應用重啟後來到的是SendCrashActivity介面,在這裡我設定了一個簡單的按鈕,點選後即可上傳崩潰資訊。程式碼比較多,這裡列一個比較有用的上傳方法吧:

  1. publicstaticString uploadFile(File file,String requestUrl){
  2. String result =null;
  3. String BOUNDARY = UUID.randomUUID().toString();//邊界標識 隨機生成
  4. String PREFIX ="--";