Android 應用程序崩潰日志捕捉


程序崩潰是應用迭代中不可避免的問題,即使有着5年或者10年經驗的程序猿也無法完全保證自己的代碼沒有任何的bug導致崩潰,現在有一些第三方平台可以幫助我們搜集應用程序的崩潰,比如友盟,詳情如下圖

雖然能夠看到崩潰的日志以及機型等,但還是不是很方便,如果需要精確定位的話需要用戶提供崩潰的時間點、機型等信息,所以最好的辦法就是我們把崩潰的信息保存在用戶的sd卡上,必要的時候發送到后台或者讓用戶手動提供一下文件,下面就來看如何實現這個功能。

Android 系統提供了處理這類問題的方法,Thread 類中提供了一個方法 setDefaultUncaughtExceptionHandler,設置了這個默認的異常處理器之后當程序發生異常之后就會回調uncaugthException()這個方法,然后可以在這個回調里面捕獲異常信息,保存到文件。

話不多說,直接上代碼:

 
         
package com.hxc.supreme.utils;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;


import com.hxc.supreme.MainApplication;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.hxc.supreme.BuildConfig.DEBUG;

/**
* UncaughtException處理類,當程序發生Uncaught異常的時候,有該類來接管程序,並記錄發送錯誤報告.
* 在Application 中調用
* CrashHandlerUtil.getInstance().init(this);
*/
public class XCrashHandlerUtils implements Thread.UncaughtExceptionHandler {
private static final String TAG = "XCrashHandlerUtils";
//系統默認的UncaughtException處理類
private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler實例
private static XCrashHandlerUtils INSTANCE = new XCrashHandlerUtils();
//程序的Context對象
private Context mContext;
//用來存儲設備信息和異常信息
private String crashTip = "似乎遇到了一點小麻煩,程序即將重新啟動";
/**
* 文件名
*/
public static final String FILE_NAME = "crash";
/**
* 異常日志 存儲位置為根目錄下的 Crash文件夾
*/
private static final String PATH = Environment.getExternalStorageDirectory().getPath() +
"/Supreme_crash/";
/**
* 文件名后綴
*/
private static final String FILE_NAME_SUFFIX = ".txt";

public String getCrashTip() {
return crashTip;
}

public void setCrashTip(String crashTip) {
this.crashTip = crashTip;
}

/**
* 保證只有一個CrashHandler實例
*/
private XCrashHandlerUtils() {
}

/**
* 獲取CrashHandler實例 ,單例模式
*
* @return 單例
*/
public static XCrashHandlerUtils getInstance() {
return INSTANCE;
}

/**
* 初始化
*
* @param context 上下文
*/
public void init(Context context) {
mContext = context;
//獲取系統默認的UncaughtException處理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//設置該CrashHandler為程序的默認處理器
Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
* 這個是最關鍵的函數,當系統中有未被捕獲的異常,系統將會自動調用 uncaughtException 方法
*
* @param thread 為出現未捕獲異常的線程
* @param ex 為未捕獲的異常 ,可以通過e 拿到異常信息
*/
@Override
public void uncaughtException(Thread thread, final Throwable ex) {
//導入異常信息到SD卡中
try {
dumpExceptionToSDCard(ex);
} catch (IOException e) {
e.printStackTrace();
}
//這里可以上傳異常信息到服務器,便於開發人員分析日志從而解決Bug
// uploadExceptionToServer();
ex.printStackTrace();
//如果系統提供了默認的異常處理器,則交給系統去結束程序,否則就由自己結束自己
if (mDefaultHandler != null) {
mDefaultHandler.uncaughtException(thread, ex);
} else {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}

}


/**
* 將異常信息寫入SD卡
*
* @param e
*/
private void dumpExceptionToSDCard(Throwable e) throws IOException {
//如果SD卡不存在或無法使用,則無法將異常信息寫入SD卡
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.w(TAG, "sdcard unmounted,skip dump exception");
return;
}
}
File dir = new File(PATH);
//如果目錄下沒有文件夾,就創建文件夾
if (!dir.exists()) {
dir.mkdirs();
}
//得到當前年月日時分秒
long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
//在定義的Crash文件夾下創建文件
File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);

try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//寫入時間
pw.println(time);
//寫入手機信息
dumpPhoneInfo(pw);
pw.println();//換行
e.printStackTrace(pw);
pw.close();//關閉輸入流
} catch (Exception e1) {
Log.e(TAG, "dump crash info failed");
}

}

/**
* 獲取手機各項信息
*
* @param pw
*/
private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
//得到包管理器
PackageManager pm = mContext.getPackageManager();
//得到包對象
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
//寫入APP版本號
pw.print("App Version: ");
pw.print(pi.versionName);
pw.print("_");
pw.println(pi.versionCode);
//寫入 Android 版本號
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);
//手機制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
//手機型號
pw.print("Model: ");
pw.println(Build.MODEL);
//CPU架構
pw.print("CPU ABI: ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
pw.println(Build.SUPPORTED_ABIS);
} else {
pw.println(Build.CPU_ABI);
}
}

}
 
        

然后再Mainapplication的onCreate中調用一下XCrashHandlerUtils.init()方法,接下來寫一個bug看一下效果,代碼很簡單

 1 package com.hxc.supreme.activity;
 2 
 3 import android.content.ComponentName;
 4 import android.content.Intent;
 5 import android.content.ServiceConnection;
 6 import android.graphics.Color;
 7 import android.os.Bundle;
 8 import android.os.IBinder;
 9 import android.support.annotation.Nullable;
10 import android.support.v7.app.AppCompatActivity;
11 import android.view.Gravity;
12 import android.view.LayoutInflater;
13 import android.view.View;
14 import android.view.ViewGroup;
15 import android.widget.Button;
16 import android.widget.LinearLayout;
17 import android.widget.TextView;
18 
19 import com.hxc.supreme.R;
20 import com.hxc.supreme.service.MainService;
21 
22 
23 /**
24  * created by huxc  on 2017/9/28.
25  * func:  ViewsTestActivity
26  * email: hxc242313@qq.com
27  */
28 
29 public class ViewsTestActivity extends AppCompatActivity implements View.OnClickListener {
30     private LinearLayout mainLayout;
31 
32     @Override
33     protected void onCreate(@Nullable Bundle savedInstanceState) {
34         super.onCreate(savedInstanceState);
35         setContentView(R.layout.activity_views_test);
36 //        mainLayout = findViewById(R.id.layout_main);
37 
38         View view = LayoutInflater.from(this).inflate(R.layout.button_view, null);
39         TextView textView = new TextView(this);
40         textView.setText("add View Dynamic");
41         textView.setGravity(Gravity.CENTER);
42         textView.setAllCaps(false);
43         textView.setTextColor(Color.RED);
44         mainLayout.addView(textView,new LinearLayout.LayoutParams(300, 200));
45     }
46 
47     @Override
48     protected void onResume() {
49         super.onResume();
50     }
51 
52     @Override
53     public void onClick(View view) {
54         switch (view.getId()) {
55         }
56     }
57 
58 }

第36行把findviewById屏蔽了,然后運行了一下程序,看一下logcat中的崩潰信息:

 

crash文件的保存路徑是Supreme_crash,看一下文件中的內容:

和控制台輸出的一毛一樣,而且還打印出了手機的型號,app的版本等相關信息,大功告成!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM