您的位置:首页 > 移动开发 > Android开发

Android之崩溃日志本地存储与远程保存

2016-03-14 15:01 477 查看
示例代码下载 : 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 方法 :

@Override

public 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 {

@Override

protected 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)

*/

@Override

public 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() {

@Override

public 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 activity

at 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.InvocationTargetException

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)

... 12 more

Caused by: java.lang.ArithmeticException: divide by zero

at cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20)

... 15 more

java.lang.reflect.InvocationTargetException

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)

Caused by: java.lang.ArithmeticException: divide by zero

at cn.org.octpus.crash.MainActivity.onClick(MainActivity.java:20)

... 15 more

java.lang.ArithmeticException: divide by zero

at 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)

------转载自http://www.bkjia.com/Androidjc/992161.html

二、奔溃日志保存到远程服务器上

实现途径 :

-- 1. 自己开发服务器端 : 在上面的保存文件的基础上, 在开发一个服务器端, 使用网络编程接口将打印到文件的内容上传到服务器端;

-- 2. 使用第三方服务 : 使用 Testin 的崩溃大师, 地址 http://crash.testin.cn/app?scnavbar ;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: