记录并通过邮件上传App崩溃日志

来源:互联网 发布:c语言for的用法 编辑:程序博客网 时间:2024/05/17 20:00

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 = { "guchuanhang@qq.com" };         String[] ccs = { "1162834643@qq.com" };        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());      }  }  

3.附上Demo
我啰嗦了折磨多,相信聪明的你,一定一看就会。Demo下载地址:
http://download.csdn.net/detail/guchuanhang/9148729

1 0