記錄並通過郵件上傳App崩潰日誌
1.引子
最近在做一個社交app的過程中,使用者總是反映app在跳轉到分享頁面的時候App無故退出。
我在我的手機上實驗了幾下,都能成功,神奇的安卓啊,最後想到了一個辦法,
記錄使用者app的崩潰日誌來解決。
可是使用者就是使用者,提交錯誤日誌,總不能給使用者說,你到xx路徑下面,把xx檔案發給我吧。
所以想到了這種方法:
1.如果app退出,則將app的崩潰日誌記錄在某個檔案下面;
2.當用戶再次開啟app的時候,提示,使用者是否上傳錯誤日誌;
3.如果使用者選擇是,就將錯誤日誌以附件的形式,新增到傳送的郵件中;
4.選擇否,就直接刪除錯誤日誌;
2.知識點講解
1.如何記錄App的崩潰日誌
/**
* UncaughtException處理類,當程式發生Uncaught異常的時候
*
* @author user 注意修改檔案的路徑和檔名,在Manifest中新增檔案讀寫許可權;
*
*/
public class CrashHandler implements UncaughtExceptionHandler {
// 錯誤日誌資料夾的位置
private String mCrashLogDirPath = "";
public static final String TAG = CrashHandler.class.getSimpleName();
// 系統預設的UncaughtException處理類
private Thread.UncaughtExceptionHandler mDefaultHandler;
// CrashHandler例項
private static CrashHandler INSTANCE = new CrashHandler();
private Context mContext;
// 用來儲存裝置資訊和異常資訊
private Map<String, String> infos = new HashMap<String, String>();
// 用於格式化日期,作為日誌檔名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss",
Locale.CHINA);
private CrashHandler() {
}
public static CrashHandler getInstance() {
return INSTANCE;
}
public void init(Context context) {
mContext = context;
mCrashLogDirPath = context.getExternalCacheDir() + File.separator
+ MainActivity.Error_DIR_NAME + File.separator;
// 獲取系統預設的UncaughtException處理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 設定該CrashHandler為程式的預設處理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 當UncaughtException發生時會轉入該函式來處理 如果匯入專案@Override報錯,請修改project編譯的jdk版本到1.5以上
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果使用者沒有處理則讓系統預設的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
} else {
// 退出程式
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}
/**
* 自定義錯誤處理,收集錯誤資訊
*
* @param ex
* @return true:如果處理了該異常資訊;否則返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
// 收集裝置引數資訊
collectDeviceInfo(mContext);
saveCrashInfo2File(ex);
// 向配置檔案中寫入標識位
flagCrash();
return true;
}
public void flagCrash() {
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(mContext).edit();
editor.putBoolean(MainActivity.TAG_OCCURRED_ERROR, true);
editor.commit();
}
/**
* 收集裝置引數資訊
*
* @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);
}
} 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
* @return Boolean 判斷檔案儲存到本地是否成功
*/
private Boolean saveCrashInfo2File(Throwable ex) {
Boolean saveFlag = false;
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();
// TODO
System.err.println("*****下面列印錯誤資訊*****");
System.err.println(result);
sb.append(result);
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".log";
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
File dir = new File(mCrashLogDirPath);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(mCrashLogDirPath
+ fileName);
fos.write(sb.toString().getBytes());
fos.close();
}
saveFlag = true;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
}
return saveFlag;
}
和這片相似的程式碼,只要稍微搜尋一下隨處可見。注意相似不是雷同。在這裡闡述一下我的小聰明:
1.在這裡我使用的路徑為this.getCacheDir(),在這個路徑下面建立和刪除檔案是不需要任何許可權的;
2.在將app崩潰資訊儲存到檔案之前,我對錯誤資訊進行了控制檯列印。因為如果僅僅將錯誤資訊儲存到本地檔案的話,對於程式設計師來說,每一次向在控制檯列印錯誤日誌,都必須在Application中將手機錯誤資訊的日誌功能關閉掉,但是下一次打包的時候,就很有可能把這件事情忘了(親身經歷啊);
3.app崩潰欄位的儲存。這裡使用 PreferenceManager
.getDefaultSharedPreferences(mContext),獲取App預設的sharedPreference檔案,省略了自定義檔案的麻煩,當然還需要自定義一個欄位。當用戶下次進入app後,對app之前是否發生過崩潰進行判斷。
3.大家注意一下錯誤資訊的差別:
1.在沒有新增錯誤日誌的時候,列印的錯誤資訊如下:
列印資訊為鮮紅色,這個大家應該都比較熟悉。
2.看一下新增記錄App崩潰日誌後的效果:
這種紅的顏色就淡了好多,就不是太惹眼了。
3.看一下報錯的錯誤日誌在手機上的效果吧:
下面貼上開啟App檢測是否發生過崩潰,上傳崩潰(通過呼叫手機的郵件系統),清除崩潰欄位和崩潰日誌的完整程式碼
/**
*
* @author guchuanhang
*
*/
public class MainActivity extends Activity implements
android.view.View.OnClickListener {
public static final int TAG_ERROR_CODE = 10;
/**
* 錯誤日誌在cachedir下面的crash資料夾下面
*/
public static final String Error_DIR_NAME = "crash";
/**
* 通過該欄位,進行判斷,程式是否發生過crash ,使用getDefaultSharedPreferences
*/
public static final String TAG_OCCURRED_ERROR = "crashed";
private Dialog upErrorDialog;
private Button mbtnMakeError = null;
private String errorString;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mbtnMakeError = (Button) findViewById(R.id.btn_make_error);
mbtnMakeError.setOnClickListener(this);
// 判斷上次是否發生過崩潰
SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
if (preferences.getBoolean(TAG_OCCURRED_ERROR, false)) {
uploadErrorDialog();
}
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_make_error:
System.out.println(errorString.equals("xxx"));
break;
default:
break;
}
}
// 第一上傳錯誤日誌的對話方塊樣式
protected void uploadErrorDialog() {
AlertDialog.Builder builder = new Builder(MainActivity.this);
builder.setMessage("上次程式異常退出,\n上傳錯誤日誌?");
builder.setTitle("提示");
builder.setPositiveButton("確定", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setAttachment(MainActivity.this);
dialog.dismiss();
}
});
builder.setNegativeButton("取消", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteErrorLogAndRemoveTag();
dialog.dismiss();
}
});
upErrorDialog = builder.create();
upErrorDialog.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (upErrorDialog != null && upErrorDialog.isShowing()) {
upErrorDialog.dismiss();
}
if (requestCode == TAG_ERROR_CODE) {
deleteErrorLogAndRemoveTag();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
public void deleteErrorLogAndRemoveTag(){
File file = new File(getExternalCacheDir(), Error_DIR_NAME);
File[] errorFiles = file.listFiles();
for (int i = 0; i < errorFiles.length; i++) {
errorFiles[i].delete();
}
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(this).edit();
editor.remove(TAG_OCCURRED_ERROR);
editor.commit();
}
public void setAttachment(Context conext) {
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
String[] tos = { "[email protected]" };
String[] ccs = { "[email protected]" };
intent.putExtra(Intent.EXTRA_EMAIL, tos);
intent.putExtra(Intent.EXTRA_CC, ccs);
intent.putExtra(Intent.EXTRA_TEXT, "我很生氣,你要儘快解決。\n 否則後果不堪設想!");
intent.putExtra(Intent.EXTRA_SUBJECT, "叮咚FM崩潰日誌");
ArrayList<Uri> imageUris = new ArrayList<Uri>();
File srcDir = new File(this.getExternalCacheDir(), "crash");
File[] logFiles = srcDir.listFiles();
for (int i = 0; i < logFiles.length; i++) {
imageUris.add(Uri.parse("file://" + logFiles[i].getAbsolutePath()));
}
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
intent.setType("image/*");
intent.setType("message/rfc882");
Intent.createChooser(intent, "Choose Email Client");
((Activity) conext).startActivityForResult(intent, TAG_ERROR_CODE);
}
}
下面貼上Application中開啟錯誤日誌的方法:
import pan.gch.demo.util.CrashHandler;
import android.app.Application;
/**
* @author guchuanhang
*
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
}
}
相關推薦
記錄並通過郵件上傳App崩潰日誌
1.引子 最近在做一個社交app的過程中,使用者總是反映app在跳轉到分享頁面的時候App無故退出。 我在我的手機上實驗了幾下,都能成功,神奇的安卓啊,最後想到了一個辦法, 記錄使用者app的崩潰日誌來解決。 可是使用者就是使用者,提交錯誤日誌,總不能給
通過Nexus搭建maven私有倉庫,並通過gradle上傳jar
pre install oss 通過 epo 選擇 配置 容器 ffffff 安裝nexus 安裝docker``·bashyum install docker-ce 拉取鏡 docker pull registry.docker-cn.com/sonatype/nex
移動端通過ajax上傳圖片(文件)並在前臺展示——通過H5的FormData對象
com 地址 ces 文件 只需要 capture val data als 前些時候遇到移動端需要上傳圖片和視頻的問題,之前一直通過ajax異步的提交數據,所以在尋找通過ajax上傳文件的方法。發現了H5裏新增了一個FormData對象,通過這個對象可以直接綁定html中
通過dockerfile釋出springweb工程並將映象上傳至私有倉庫
工程結構 dockerfile檔案內容 # 這個映象的基礎映象是alpine FROM alpine # 作者 MAINTAINER test.com "[email protected]" # 配置社群源 RUN echo -
android app崩潰日誌收集以及上傳
已經做成sdk的形式,原始碼已公開,原始碼看不懂的請自行google。 如果想定製適應自己app的sdk請自行fork。 AndroidLogCollector android app崩潰日誌收集sdk 1.0 作者:賈博士 崩潰日誌收集方法: 1.L
app上傳至AppStore記錄 及更新版本上傳
1、建立證書(建立生產證書) 建立證書 建立證書的這個過程需要以前用到的鑰匙串建立的檔案 將那檔案上傳後 建立生產證書成功 . 下載安裝即可 建立配置檔案 編輯個配置檔案的名稱 選擇證書 ,下一步即可,建立成功後 下載即可。 下載完成後 請雙擊安裝證書 2、程式打
windows學習記錄之MFC通過URL上傳下載檔案
** HTTP四種常見的POST提交資料方式: ** HTTP/1.1 協議規定的 HTTP 請求方法有 OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 這幾種。其中 POST 一般用來向服務端提交資料,本
Android開發之app崩潰日誌收集以及上傳
已經做成sdk的形式,原始碼已公開,原始碼看不懂的請自行google。 如果想定製適應自己app的sdk請自行fork。 AndroidLogCollector android app崩潰日誌收集sdk 1.0 作者:賈博士 崩潰日誌收集方法: 1.Lo
Android端通過HttpURLConnection上傳文件到server
cache ddr not turn popu real god reat rep Android端通過HttpURLConnection上傳文件到server一:實現原理近期在做Androidclient的應用開發,涉及到要把圖片上傳到後臺server中。自己選擇了做S
java通過sftp上傳文件
lin div 連接 identity 密鑰認證 工具類 ransient equals put 轉載:http://blog.csdn.net/yhl_jxy/article/details/72633034 Linux操作系統我們經常使用ssh中的ftp,sftp連接服
linux學習記錄-----vsftpd文件上傳(550 create directory operation failed)
opera operation chmod sel create 關閉 用戶名 clas ati 1、連接條件:服務端服務開啟,防火墻關閉 2、ftp服務器的路徑可手動配置,默認為:var/ftp/pub,必須確保pub目錄有足夠的權限 3、匿名登陸的用戶名為:anonym
PHP5.6通過CURL上傳圖片@符無效的兼容問題
本地 之前 做了 num tran 新的 需要 keyword 找到 今天本來想試試一個圖片雲的API,於是本地做了個上傳圖片的測試,結果灰常郁悶的發現以前一直用的好好的CURL上傳圖片居然死活不起作用,本來幾分鐘搞定的事情,結果折騰了大半天才終於找到原因,居然是兼容性問題
bootstrap改變上傳文件按鈕樣式,並顯示已上傳文件名
-a BE ner city 定義 lock left contain ont 參考博文: html中,文件上傳時使用的<input type="file">的樣式自定義html中<input type="file">默認樣式很醜,這裏用了Boot
asp.net core 通過ajax上傳圖片及wangEditor圖片上傳
images use class multi jquery 開始 load als org asp.net core 通過ajax上傳圖片 .net core前端代碼,因為是通過ajax調用,首先要保證ajax能調用後臺代碼,具體參見上一篇.net core 使用ajax
Asp.Net MVC 中JS通過ajaxfileupload上傳圖片獲取身份證姓名、生日、家庭住址等詳細信息
新手上路 pri virt them boolean tac 識別 multipart utf 客戶要求用身份證圖片上傳獲取身份證的詳細信息就下來研究了一下(現在的客戶真的懶 身份證信息都懶得輸入了哈哈...),經過慢慢研究,果然皇天不負有心人搞出來了。這個借助的是騰訊
spring cloud實戰與思考(二) 微服務之間通過fiegn上傳多個文件1
jar 多文件 上傳文件 ret nmap spa 不同 port 問題 需求場景: 微服務之間調用接口一次性上傳多個文件。 上傳文件的同時附帶其他參數。 多個文件能有效的區分開,以便進行不同處理。 Spring cloud的微服務之間接口調用使用Feign。原裝的
spring cloud實戰與思考(三) 微服務之間通過fiegn上傳一組文件(下)
ets inf str ceo iter protected let pan ins 需求場景: 用戶調用微服務1的接口上傳一組圖片和對應的描述信息。微服務1處理後,再將這組圖片上傳給微服務2進行處理。各個微服務能區分開不同的圖片進行不同處理。 上一篇博客已經討
python追蹤小說更新並通過郵件提醒
ID lse ini ret end href string pan xxxxxx #!/usr/bin/env python #coding:utf-8 from urllib import request, parseimport os import time imp
通過前端上傳圖片等文件的方法
一個 click spa string 追加 fun span 框架 new 1 <script type="text/javascript"> 2 3 ##先禁止form表自動提交 4 $(‘form‘).submit(function () {
上傳App Store報錯 ERROR ITMS-90171,90209
port inf res 刪除 store program erro 上傳 tab ERROR ITMS-90171: "Invalid Bundle Structure - The binary file ERROR ITMS-90209: "Invalid Segme