【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存
来源:互联网 发布:淘宝冻结账号解冻中心 编辑:程序博客网 时间:2024/06/13 09:46
示例代码下载 : 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=pxa1088RADIO=unknownversionCode=1HOST=SWDA2601TAGS=release-keysID=JDQ39MANUFACTURER=samsungTYPE=userIS_TRANSLATION_ASSISTANT_ENABLED=falseIS_SECURE=falseTIME=1416298944000FINGERPRINT=samsung/wilcoxdszn/wilcoxds:4.2.2/JDQ39/G3812ZNUANK1:user/release-keysUNKNOWN=unknownBOARD=PXA1088PRODUCT=wilcoxdsznversionName=1.0DISPLAY=JDQ39.G3812ZNUANK1USER=se.infraDEVICE=wilcoxdsMODEL=SM-G3812BOOTLOADER=unknownCPU_ABI=armeabi-v7aCPU_ABI2=armeabiIS_SYSTEM_SECURE=falseIS_DEBUGGABLE=falseSERIAL=5202889565301100BRAND=samsungjava.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 moreCaused by: java.lang.ArithmeticException: divide by zeroat cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20)... 15 morejava.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 morejava.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.InvocationTargetException04-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 more04-27 20:11:40.890: E/TestinAgent(16432): Caused by: java.lang.ArithmeticException: divide by zero04-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 more04-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-G381204-27 20:11:40.890: E/TestinAgent(16432): OS: 4.2.204-27 20:11:40.890: E/TestinAgent(16432): AppVersion: 1.004-27 20:11:40.890: E/TestinAgent(16432): PackageName: cn.org.octpus.crash04-27 20:11:40.890: E/TestinAgent(16432): Activity: MainActivity04-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 MB04-27 20:11:40.890: E/TestinAgent(16432): Userinfo: 04-27 20:11:40.890: E/TestinAgent(16432): ****************************************
(2) 后台信息
后台崩溃数据 :
-- 控制台信息 : 进入崩溃大师的控制台, 就会看到刚看到的 CrashInfoDemo 应用, 此时我们估计产生的异常已经打印出来了;
-- 点击进入该应用的详情 :
-- 详细的崩溃信息查看 :
- 【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存
- Android之崩溃日志本地存储与远程保存
- Android 崩溃日志 本地存储 与 远程保存
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
- Android 异常崩溃日志,捕捉并保存到本地
- Android采集应用崩溃日志
- android应用调试之如何保存logcat日志到本地
- android应用调试之如何保存logcat日志到本地
- NGINX配置HTTPS加密反向代理访问–自签CA
- Android Studio入门到精通
- linux sed删除空行和空格符命令
- Apple Watch开发(三)--WatchKit简介和Watch适配、布局
- leetcode || 122、Best Time to Buy and Sell Stock II
- 【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存
- Android中Cannot draw recycled bitmaps
- java.io.IOException: org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode =
- 代码流程分析二:Settings-蓝牙分析-新增rawgelWhile分析一
- 在WINDOWS中设置计划任务执行PHP文件
- C和指针读书笔记-第12章(使用结构和指针)
- spark:仿写案例--30
- 实用算法的分析与程序设计——递推法(倒推法)
- vector.....