diff --git a/package.json b/package.json new file mode 100644 index 0000000..36c758a --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "crash-log-cordova-plugin", + "version": "1.0.0", + "description": "save crash info to log file.", + "cordova": { + "id": "crash-log-cordova-plugin", + "platforms": [ + "android" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/shuto-cn/crash-log-cordova-plugin.git" + }, + "keywords": [ + "cordova", + "crash", + "log" + ], + "author": "shuto", + "license": "MIT" +} \ No newline at end of file diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..0b208f2 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,23 @@ + + + CrashLog + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/android/cn/shuto/crash/CrashHandler.java b/src/android/cn/shuto/crash/CrashHandler.java new file mode 100644 index 0000000..47bbbc2 --- /dev/null +++ b/src/android/cn/shuto/crash/CrashHandler.java @@ -0,0 +1,185 @@ +package cn.shuto.crash; + +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; + +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; + +public class CrashHandler implements UncaughtExceptionHandler { + + public static final String TAG = "CrashHandler"; + + //系统默认的UncaughtException处理类 + private Thread.UncaughtExceptionHandler mDefaultHandler; + //CrashHandler实例 + private static CrashHandler INSTANCE = new CrashHandler(); + //程序的Context对象 + private Context mContext; + //用来存储设备信息和异常信息 + private Map infos = new HashMap(); + + //用于格式化日期,作为日志文件名的一部分 + private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + + /** 保证只有一个CrashHandler实例 */ + private CrashHandler() { + } + + /** 获取CrashHandler实例 ,单例模式 */ + public static CrashHandler getInstance() { + return INSTANCE; + } + + /** + * 初始化 + * + * @param context + */ + public void init(Context context) { + mContext = context; + //获取系统默认的UncaughtException处理器 + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + //设置该CrashHandler为程序的默认处理器 + Thread.setDefaultUncaughtExceptionHandler(this); + } + + /** + * 当UncaughtException发生时会转入该函数来处理 + */ + @Override + public void uncaughtException(Thread thread, Throwable ex) { + if (!handleException(ex) && mDefaultHandler != null) { + //如果用户没有处理则让系统默认的异常处理器来处理 + mDefaultHandler.uncaughtException(thread, ex); + } else { + try { + Thread.sleep(1500); + } 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来显示异常信息 + 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 + ""; + 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 返回文件名称,便于将文件传送到服务器 + */ + private String saveCrashInfo2File(Throwable ex) { + + StringBuffer sb = new StringBuffer(); + for (Map.Entry 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(); + 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)) { + String path = "/sdcard/crash/"; + 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; + } +} diff --git a/src/android/cn/shuto/crash/CrashLogPlugin.java b/src/android/cn/shuto/crash/CrashLogPlugin.java new file mode 100644 index 0000000..7d951da --- /dev/null +++ b/src/android/cn/shuto/crash/CrashLogPlugin.java @@ -0,0 +1,19 @@ +package cn.shuto.crash; + +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaWebView; + +/** + * 应用崩溃日志文件插件 + */ +public class CrashLogPlugin extends CordovaPlugin { + + @Override + public void initialize(CordovaInterface cordova, CordovaWebView webView) { + super.initialize(cordova, webView); + CrashHandler crashHandler = CrashHandler.getInstance(); + crashHandler.init(cordova.getContext()); + } + +} diff --git a/www/crash-log.js b/www/crash-log.js new file mode 100644 index 0000000..ac653ad --- /dev/null +++ b/www/crash-log.js @@ -0,0 +1,5 @@ +var exec = require('cordova/exec'); + +exports.coolMethod = function (arg0, success, error) { + exec(success, error, 'CrashLog', 'coolMethod', [arg0]); +};