【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存
示例代碼下載 :?http://download.csdn.net/detail/han1202012/8638801;
一. 崩潰日志本地存儲
1. 保存原理解析
崩潰信息本地保存步驟 :?
-- 1. 自定義類實現?UncaughtExceptionHandler :?public class CrashHandler implements UncaughtExceptionHandler;
-- 2. 設置該自定義的?CrashHandler?類為單例模式 :?
// 單例模式private static CrashHandler INSTANCE = new CrashHandler();private CrashHandler() {}public static CrashHandler getInstance() {return INSTANCE;}-- 重寫 uncaughtException?方法 :? @Overridepublic void uncaughtException(Thread thread, Throwable ex)-- 自定義?handleException 方法處理異常信息 : 在該方法中進行設備信息收集, 以及將信息保存到文件中;(1) UncaughtExceptionHandler 類解析
UncaughtExceptionHandler 作用 : 該類處理以下情況, 如果有未捕獲的異常發生, 出現了程序崩潰閃退的情況, 此時會回調該類的?uncaughtException 方法;
(2) 線程相關
線程相關 : 每個線程都對應有響應的默認的未捕獲異常處理器;
-- 獲取線程默認的未捕獲異常處理器 :?Thread.getDefaultUncaughtExceptionHandler();
-- 設置線程默認的未捕獲異常處理器 :?Thread.setDefaultUncaughtExceptionHandler(this);
(3)?uncaughtException 方法
uncaughtException 方法解析 :?
-- 回調時機 : 出現未定義的異常時;
-- 回調參數 : 回調時會傳入 線程對象 和 要拋出的異常信息, 我們可以在程序中拿到這兩個信息;
public void uncaughtException(Thread thread, Throwable ex)
(4) 手機設備信息
手機設備信息手機步驟 :?
-- 1. 獲取包信息 :?
//獲取包管理器PackageManager pm = ctx.getPackageManager();//獲取包信息PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),PackageManager.GET_ACTIVITIES);-- 2. 獲取版本號信息 :?
if (pi != null) {//版本號String versionName = pi.versionName == null ? "null": pi.versionName;//版本代碼String versionCode = pi.versionCode + "";//將版本信息存放到 成員變量 Map<String, String> mInfos 中this.mInfos.put("versionName", versionName);this.mInfos.put("versionCode", versionCode);}
-- 3. 使用反射獲取 Build 類成員變量變量 , 并遍歷獲取這些變量內容:?
//獲取 Build 中定義的變量, 使用反射方式獲取, 該類中定義了設備相關的變量信息Field[] fields = Build.class.getDeclaredFields();//遍歷獲取額變量, 將這些信息存放到成員變量 Map<String, String> mInfos 中for (Field field : fields) {try {//設置 Build 成員變量可訪問field.setAccessible(true);//將 設備相關的信息存放到 mInfos 成員變量中mInfos.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);}}
(4) 保存崩潰信息到文件
保存文件步驟 : 這些步驟就很簡單了, 使用 IO流即可;
-- 1. 將之前獲取的 Build 設備信息, 版本信息, 崩潰信息轉為字符串 :?
//存儲相關的字符串信息StringBuffer sb = new StringBuffer();//將成員變量 Map<String, String> mInfos 中的數據 存儲到 StringBuffer sb 中for (Map.Entry<String, String> entry : this.mInfos.entrySet()) {String key = entry.getKey();String value = entry.getValue();sb.append(key + "=" + value + "\n");}-- 2. 在 Logcat 中打印崩潰信息 : 之前的默認操作就是打印崩潰信息到 Logcat 中, 我們在這里繼續執行完這個步驟, 否則Logcat 中沒有數據的;
//將 StringBuffer sb 中的字符串寫出到文件中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();-- 3. 寫出數據到文件中 : IO 流知識點, 不再做過多贅述;
String result = writer.toString();sb.append(result);try {long timestamp = System.currentTimeMillis();String time = formatter.format(new Date());String fileName = "crash-" + time + "-" + timestamp + ".txt";if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//獲取文件輸出路徑String path = Environment.getExternalStorageDirectory()+ "/crashinfo/";//創建文件夾和文件File dir = new File(path);if (!dir.exists()) {dir.mkdirs();}//創建輸出流FileOutputStream fos = new FileOutputStream(path + fileName);//向文件中寫出數據fos.write(sb.toString().getBytes());fos.close();}return fileName;} catch (Exception e) {Log.e(TAG, "an error occured while writing file...", e);}
2. 代碼及示例
(1) 相關代碼示例
故意發生錯誤的代碼 :?
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void onClick(View view) {int i = 3;i = i / 0;}}
CrashHandler 注冊代碼 : 在 Activity 或者 Application 中注冊該代碼;
CrashHandler.getInstance().init(getApplicationContext());
CrashHandler 代碼 :?
package cn.org.octpus.crash;import java.io.File; import java.io.FileOutputStream; 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.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Environment; import android.os.Looper; import android.util.Log; import android.widget.Toast;/*** UncaughtExceptionHanlder 作用 : 處理 線程被未捕獲的異常終止 的情況, 一旦出現了未捕獲異常崩潰, 系統就會回調該類的* uncaughtException 方法;*/ public class CrashHandler implements UncaughtExceptionHandler {// 用于打印日志的 TAG 標識符public static final String TAG = "octopus.CrashHandler";// 系統默認的UncaughtException處理類private Thread.UncaughtExceptionHandler mDefaultHandler;// 程序的Context對象private Context mContext;// 用來存儲設備信息和異常信息private Map<String, String> mInfos = new HashMap<String, String>();// 用于格式化日期,作為日志文件名的一部分private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");// 單例模式private static CrashHandler INSTANCE = new CrashHandler();private CrashHandler() {}public static CrashHandler getInstance() {return INSTANCE;}/*** 初始化該類, 向系統中注冊* @param context*/public void init(Context context) {mContext = context;// 獲取系統默認的 UncaughtException 處理器mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 設置該 CrashHandler 為程序的默認處理器Thread.setDefaultUncaughtExceptionHandler(this);}/** 出現未捕獲的異常時, 會自動回調該方法* (non-Javadoc)* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)*/@Overridepublic void uncaughtException(Thread thread, Throwable ex) {/** 調用 handleException() 方法處理該線程* 如果返回 true 說明處理成功, 如果返回 false 則調用默認的異常處理器來處理* 一般情況下該方法都會成功處理*/if (!handleException(ex) && mDefaultHandler != null) {// 如果用戶沒有處理則讓系統默認的異常處理器來處理mDefaultHandler.uncaughtException(thread, ex);} else {try {Thread.sleep(3000);} catch (InterruptedException e) {Log.e(TAG, "error : ", e);}// 退出程序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;}/** 使用Toast來顯示異常信息, * 由于在主線程會阻塞, * 不能實時出現 Toast 信息, * 這里我們在子線程中處理 Toast 信息*/new Thread() {@Overridepublic void run() {Looper.prepare();Toast.makeText(mContext, "很抱歉,程序出現異常,即將退出.", Toast.LENGTH_LONG).show();Looper.loop();}}.start();// 收集設備參數信息collectDeviceInfo(mContext);// 保存日志文件saveCrashInfo2File(ex);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 + "";//將版本信息存放到 成員變量 Map<String, String> mInfos 中this.mInfos.put("versionName", versionName);this.mInfos.put("versionCode", versionCode);}} catch (NameNotFoundException e) {Log.e(TAG, "an error occured when collect package info", e);}//獲取 Build 中定義的變量, 使用反射方式獲取, 該類中定義了設備相關的變量信息Field[] fields = Build.class.getDeclaredFields();//遍歷獲取額變量, 將這些信息存放到成員變量 Map<String, String> mInfos 中for (Field field : fields) {try {//設置 Build 成員變量可訪問field.setAccessible(true);//將 設備相關的信息存放到 mInfos 成員變量中mInfos.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 返回文件名稱,便于將文件傳送到服務器*/private String saveCrashInfo2File(Throwable ex) {//存儲相關的字符串信息StringBuffer sb = new StringBuffer();//將成員變量 Map<String, String> mInfos 中的數據 存儲到 StringBuffer sb 中for (Map.Entry<String, String> entry : this.mInfos.entrySet()) {String key = entry.getKey();String value = entry.getValue();sb.append(key + "=" + value + "\n");}//將 StringBuffer sb 中的字符串寫出到文件中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);try {long timestamp = System.currentTimeMillis();String time = formatter.format(new Date());String fileName = "crash-" + time + "-" + timestamp + ".txt";if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//獲取文件輸出路徑String path = Environment.getExternalStorageDirectory()+ "/crashinfo/";//創建文件夾和文件File dir = new File(path);if (!dir.exists()) {dir.mkdirs();}//創建輸出流FileOutputStream fos = new FileOutputStream(path + fileName);//向文件中寫出數據fos.write(sb.toString().getBytes());fos.close();}return fileName;} catch (Exception e) {Log.e(TAG, "an error occured while writing file...", e);}return null;} }
(2) 結果示例
崩潰日志存放文件路徑 :?/storage/sdcard0/crashinfo/crash-2015-04-27-19-31-41-1430134301642.txt;
-- 說明 : 其中?/storage/sdcard0/ 是系統默認的 SD 卡路徑,?crashinfo/crash-2015-04-27-19-31-41-1430134301642.txt 是我們創建的文件;
崩潰日志內容 :?
1430134301642.txt < HARDWARE=pxa1088 RADIO=unknown versionCode=1 HOST=SWDA2601 TAGS=release-keys ID=JDQ39 MANUFACTURER=samsung TYPE=user IS_TRANSLATION_ASSISTANT_ENABLED=false IS_SECURE=false TIME=1416298944000 FINGERPRINT=samsung/wilcoxdszn/wilcoxds:4.2.2/JDQ39/G3812ZNUANK1:user/release-keys UNKNOWN=unknown BOARD=PXA1088 PRODUCT=wilcoxdszn versionName=1.0 DISPLAY=JDQ39.G3812ZNUANK1 USER=se.infra DEVICE=wilcoxds MODEL=SM-G3812 BOOTLOADER=unknown CPU_ABI=armeabi-v7a CPU_ABI2=armeabi IS_SYSTEM_SECURE=false IS_DEBUGGABLE=false SERIAL=5202889565301100 BRAND=samsung java.lang.IllegalStateException: Could not execute method of the activityat android.view.View$1.onClick(View.java:3804)at android.view.View.performClick(View.java:4439)at android.widget.Button.performClick(Button.java:142)at android.view.View$PerformClick.run(View.java:18395)at android.os.Handler.handleCallback(Handler.java:725)at android.os.Handler.dispatchMessage(Handler.java:92)at android.os.Looper.loop(Looper.java:176)at android.app.ActivityThread.main(ActivityThread.java:5319)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:511)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.reflect.InvocationTargetExceptionat java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:511)at android.view.View$1.onClick(View.java:3799)... 12 more Caused by: java.lang.ArithmeticException: divide by zeroat cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20)... 15 more java.lang.reflect.InvocationTargetExceptionat java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:511)at android.view.View$1.onClick(View.java:3799)at android.view.View.performClick(View.java:4439)at android.widget.Button.performClick(Button.java:142)at android.view.View$PerformClick.run(View.java:18395)at android.os.Handler.handleCallback(Handler.java:725)at android.os.Handler.dispatchMessage(Handler.java:92)at android.os.Looper.loop(Looper.java:176)at android.app.ActivityThread.main(ActivityThread.java:5319)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:511)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.ArithmeticException: divide by zeroat cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20)... 15 more java.lang.ArithmeticException: divide by zeroat cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:511)at android.view.View$1.onClick(View.java:3799)at android.view.View.performClick(View.java:4439)at android.widget.Button.performClick(Button.java:142)at android.view.View$PerformClick.run(View.java:18395)at android.os.Handler.handleCallback(Handler.java:725)at android.os.Handler.dispatchMessage(Handler.java:92)at android.os.Looper.loop(Looper.java:176)at android.app.ActivityThread.main(ActivityThread.java:5319)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:511)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)at dalvik.system.NativeStart.main(Native Method)
二. 崩潰日志保存到遠程服務器
實現途徑 :?
-- 1. 自己開發服務器端 : 在上面的保存文件的基礎上, 在開發一個服務器端, 使用網絡編程接口將打印到文件的內容上傳到服務器端;
-- 2. 使用第三方服務 : 使用 Testin 的崩潰大師, 地址?http://crash.testin.cn/app?scnavbar ;
1. 集成崩潰大師步驟
(1) 創建應用 獲取 key 值
創建應用步驟 :?
-- 1. 創建界面 :?http://crash.testin.cn/apm/task/create ;
-- 2. 填寫一個應用信息 :?
-- 3. 獲取 key : 點擊"提交并獲取 Appkey" 按鈕, 獲取到了 AppKey "0da6263ca1f5b84a2dd405b07227f483";
(2) 設置類型
第二步設置類型, 選擇默認的應用即可 :?
(3) 下載 jar 包
jar 包簡介 : 集成崩潰大師, 只需要集成一個 jar 包即可, 點擊如下按鈕即可下載 該 jar 包;
-- jar 包內容 : 下載后解壓, jar 包名稱是?testinagent.jar ;
(4) 導入 SDK?
導入 SDK : 在應用下 創建一個 libs 目錄, 將jar 包拷貝進去即可;
(5) 配置 AndroidManifest.xml 文件?
在配置文件中添加如下用戶權限即可 :?
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.READ_LOGS" /><uses-permission android:name="android.permission.GET_TASKS" />
(6) 初始化設置
在代碼中注冊 :?
//初始化 Testin 崩潰大師TestinAgent.init(getApplicationContext(), "0da6263ca1f5b84a2dd405b07227f483", "octopus");
2. 測試示例
(1) 日志信息
測試成功會打印如下信息 :?
04-27 20:11:40.890: E/TestinAgent(16432): **************************************** 04-27 20:11:40.890: E/TestinAgent(16432): A Java crash caught by TestinAgent, pkg=cn.org.octpus.crash 04-27 20:11:40.890: E/TestinAgent(16432): ---------------------------------------- 04-27 20:11:40.890: E/TestinAgent(16432): reason: java.lang.IllegalStateException: Could not execute method of the activity 04-27 20:11:40.890: E/TestinAgent(16432): ---------------------------------------- 04-27 20:11:40.890: E/TestinAgent(16432): stacktrace: 04-27 20:11:40.890: E/TestinAgent(16432): at android.view.View$1.onClick(View.java:3804) 04-27 20:11:40.890: E/TestinAgent(16432): at android.view.View.performClick(View.java:4439) 04-27 20:11:40.890: E/TestinAgent(16432): at android.widget.Button.performClick(Button.java:142) 04-27 20:11:40.890: E/TestinAgent(16432): at android.view.View$PerformClick.run(View.java:18395) 04-27 20:11:40.890: E/TestinAgent(16432): at android.os.Handler.handleCallback(Handler.java:725) 04-27 20:11:40.890: E/TestinAgent(16432): at android.os.Handler.dispatchMessage(Handler.java:92) 04-27 20:11:40.890: E/TestinAgent(16432): at android.os.Looper.loop(Looper.java:176) 04-27 20:11:40.890: E/TestinAgent(16432): at android.app.ActivityThread.main(ActivityThread.java:5319) 04-27 20:11:40.890: E/TestinAgent(16432): at java.lang.reflect.Method.invokeNative(Native Method) 04-27 20:11:40.890: E/TestinAgent(16432): at java.lang.reflect.Method.invoke(Method.java:511) 04-27 20:11:40.890: E/TestinAgent(16432): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102) 04-27 20:11:40.890: E/TestinAgent(16432): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869) 04-27 20:11:40.890: E/TestinAgent(16432): at dalvik.system.NativeStart.main(Native Method) 04-27 20:11:40.890: E/TestinAgent(16432): Caused by: java.lang.reflect.InvocationTargetException 04-27 20:11:40.890: E/TestinAgent(16432): at java.lang.reflect.Method.invokeNative(Native Method) 04-27 20:11:40.890: E/TestinAgent(16432): at java.lang.reflect.Method.invoke(Method.java:511) 04-27 20:11:40.890: E/TestinAgent(16432): at android.view.View$1.onClick(View.java:3799) 04-27 20:11:40.890: E/TestinAgent(16432): ... 12 more 04-27 20:11:40.890: E/TestinAgent(16432): Caused by: java.lang.ArithmeticException: divide by zero 04-27 20:11:40.890: E/TestinAgent(16432): at cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20) 04-27 20:11:40.890: E/TestinAgent(16432): ... 15 more 04-27 20:11:40.890: E/TestinAgent(16432): ---------------------------------------- 04-27 20:11:40.890: E/TestinAgent(16432): deviceinfo: 04-27 20:11:40.890: E/TestinAgent(16432): Device: samsung/SM-G3812 04-27 20:11:40.890: E/TestinAgent(16432): OS: 4.2.2 04-27 20:11:40.890: E/TestinAgent(16432): AppVersion: 1.0 04-27 20:11:40.890: E/TestinAgent(16432): PackageName: cn.org.octpus.crash 04-27 20:11:40.890: E/TestinAgent(16432): Activity: MainActivity 04-27 20:11:40.890: E/TestinAgent(16432): Total Disk Space: 2328 MB ; Free Disk Space: 1688 MB 04-27 20:11:40.890: E/TestinAgent(16432): Total SD Space: 2308 MB ; Free SD Space: 1668 MB 04-27 20:11:40.890: E/TestinAgent(16432): CPU Usage: 17.742 % 04-27 20:11:40.890: E/TestinAgent(16432): Memory Usage: 12 MB 04-27 20:11:40.890: E/TestinAgent(16432): Userinfo: 04-27 20:11:40.890: E/TestinAgent(16432): ****************************************
(2) 后臺信息
后臺崩潰數據 :?
-- 控制臺信息 : 進入崩潰大師的控制臺, 就會看到剛看到的 CrashInfoDemo 應用, 此時我們估計產生的異常已經打印出來了;
-- 點擊進入該應用的詳情 :?
-- 詳細的崩潰信息查看 :?
總結
以上是生活随笔為你收集整理的【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【流媒体开发】VLC Media Pla
- 下一篇: 【OpenGL ES】 Android