程序崩潰是應用迭代中不可避免的問題,即使有着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的版本等相關信息,大功告成!