【Android】Android Crash之异常信息反馈机制

1.为什么需要反馈Crash报告?
       Crash最通俗直观的感受就是App软件出现崩溃导致的闪退等现象,在Android原生态下会出现一个App Force Close的Dialog,但是对于用户体验相当不好。Crash的产生是不可避免的,它产生的原因可能来自于Android底层的Bug,或是因为网络不畅,又或者是手机适配性问题,更严重的是代码质量不过关。Crash的产生是我们最不愿意看到的,即使我们花费大量时间进行测试,测试,再测试,bug还是会产生,为了能够获取到Crash信息,正式的软件中都会有Crash的反馈机制,开发人员会根据这些Crash信息对软件进行改进。
2.如何获取这些Crash信息?
      1)首先,先看这样一个方法
    /**     * Sets the default uncaught exception handler. This handler is invoked in     * case any Thread dies due to an unhandled exception.     *     * @param handler     *            The handler to set or null.     */    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {        Thread.defaultUncaughtHandler = handler;    }
       这个方法可以设置系统的默认异常处理器,我们可以利用这个方法解决应用中常见的crash问题。当crash发生的时候,我们可以捕获到异常信息,把异常信息存储到SD卡中,然后在合适的时机通过网络将crash信息上传到服务器上,这样开发人员就可以分析用户crash的场景从而在后面的版本中修复此类crash。我们还可以在crash发生时,弹出一个通知告诉用户程序crash了,然后再退出,这样做比闪退要温和一点。
       2)再看一个Java中的接口——UncaughtExceptionHandler

static interface


  Thread.UncaughtExceptionHandler
           当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。


我们可以实现这个接口,然后在这个接口中的uncaughtException(Thread thread, Throwable ex)方法中实现对Crash信息的捕获。
3.思路描述
/*         * 1.捕获异常信息;         * 2.上报手机和App的相关信息         *   1).VersionCode         *   2).VersionName         *   3).PackageName         *   4).手机分辨率         *   5).手机系统版本号         *   6).手机品牌         *   7).手及型号         *   8).CPU架构         * 3.将以上信息写入指定的Crash日志文件中         *   1)假如写入SDCard中,则首先判断SDCard是否可用         *   2)假如可用,判断指定文件夹是否存在         *   3)如果不存在则创建一个文件夹,如果存在继续         *   4)常见一个以当前时间为文件名的日志文件并将Crash信息写入         * 4.适当的时机将Crash日志信息上传到服务器         *      1)Crash产生后,如果网络等条件允许则立即上传         *   2)如果网络等条件有限,则软件下次启动时检测有无Crash信息         *   3)如果有且网络正常,上传到服务器,否则重复(3)         */
4.具体实现
1)实现异常处理CrashHandler
package com.example.crash.overall; import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import java.lang.Thread.UncaughtExceptionHandler;import java.text.SimpleDateFormat;import java.util.Date; import com.example.crash.utils.AppUtil; import android.content.Context;import android.os.Environment;import android.os.Process;import android.util.Log; /** * Description: Crash异常信息搜集类 *  * @author danDingCongRong * @Version 1.0.0 * @Created at 2014-7-28 14:06:49 * @Modified by [作者] on [修改日期] */public class CrashHandler implements UncaughtExceptionHandler {     private static final String TAG = "CrashHandler";     private Context context;     private UncaughtExceptionHandler defaultExceptionHandler;     private static CrashHandler crashHandler;     private CrashHandler() {        ;    }     // 单例模式    public static CrashHandler getInstance() {        if (null == crashHandler) {            crashHandler = new CrashHandler();        }         return crashHandler;    }     public void init(Context context) {        // 系统默认的异常处理器        defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();        // 将当前实例设为系统默认的异常处理器        Thread.setDefaultUncaughtExceptionHandler(this);        // 获取App信息时用户此参数        context = context.getApplicationContext();    }     @Override    public void uncaughtException(Thread thread, Throwable ex) {         // 导出崩溃信息到日志文件        dumpExceptionToSDCard(ex);         // 打印崩溃信息到Log日志        ex.printStackTrace();         // 如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己        if (null != defaultExceptionHandler) {            defaultExceptionHandler.uncaughtException(thread, ex);        } else {            Process.killProcess(Process.myPid());        }    }     // 导出崩溃信息到日志文件    private void dumpExceptionToSDCard(Throwable ex) {        // 检测SDCard是否可用--如果不可以在log中给予提醒        if (!Environment.getExternalStorageState().equals(                Environment.MEDIA_MOUNTED)) {            Log.w(TAG, "SDCard unmounted!");            return;        }         // 判断Crash文件夹是否存在,如果不存在则创建        File crashDir = new File(Constants.CRASH_FILE_PATH);        if (!crashDir.exists()) {            crashDir.mkdir();        }         // 创建当前崩溃日志文件        String currentTime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")                .format(new Date(System.currentTimeMillis()));        String logFileName = currentTime + ".txt";        File logFile = new File(crashDir, logFileName);         BufferedWriter bufferedWriter = null;        PrintWriter printWriter = null;        try {            bufferedWriter = new BufferedWriter(new FileWriter(logFile));            printWriter = new PrintWriter(bufferedWriter);             // 崩溃发生时间            printWriter.write(currentTime);            // 崩溃手机的系统信息及其用户所用的软件信息            String phoneInfo = new AppUtil(context).getPhoneInfoToCrash();            printWriter.write(phoneInfo);            // 崩溃信息            ex.printStackTrace(printWriter);             printWriter.flush();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (null != bufferedWriter) {                try {                    bufferedWriter.close();                } catch (IOException e) {                    e.printStackTrace();                }            }             if (null != printWriter) {                printWriter.close();            }        }    } }
2)获取手机及App软件等相关信息
package com.example.crash.utils; import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException; /** * Description: 用户获取软件信息和系统信息的工具 *  * @author danDingCongRong * @Version 1.0.0 * @Created at 2014-7-28 15:05:41 * @Modified by [作者] on [修改日期] */public class AppUtil {     private Context context;     public AppUtil(Context context) {        this.context = context;    }     // 获取App的VersionCode    public int getAppVersionCode() {        PackageManager packageManager = context.getPackageManager();         PackageInfo packageInfo = null;        try {            packageInfo = packageManager.getPackageInfo(                    context.getPackageName(), PackageManager.GET_ACTIVITIES);        } catch (NameNotFoundException e) {            e.printStackTrace();        }         int versionCode = 0;        if (null != packageInfo) {            versionCode = packageInfo.versionCode;        }         return versionCode;    }     // 获取App的VersionName    public String getAppVersionName() {        PackageManager packageManager = context.getPackageManager();         PackageInfo packageInfo = null;        try {            packageInfo = packageManager.getPackageInfo(                    context.getPackageName(), PackageManager.GET_ACTIVITIES);        } catch (NameNotFoundException e) {            e.printStackTrace();        }         return packageInfo.versionName;    }     public String getPhoneInfoToCrash() {        StringBuilder stringBuilder = new StringBuilder();         stringBuilder.append("PackageName:").append(context.getPackageName())                .append('\n');        stringBuilder.append("VesionCode:").append(getAppVersionCode())                .append('\n');        stringBuilder.append("VersionName:").append(getAppVersionName())                .append('\n');        stringBuilder.append("OS Version:")                .append(android.os.Build.VERSION.RELEASE).append('_')                .append(android.os.Build.VERSION.SDK_INT).append('\n');        stringBuilder.append("Model:").append(android.os.Build.MODEL)                .append('\n');        stringBuilder.append("Manufacturer:")                .append(android.os.Build.MANUFACTURER).append('\n');        stringBuilder.append("CPU:").append(android.os.Build.CPU_ABI)                .append('\n');         return stringBuilder.toString();    }}
3)为UI线程添加默认异常事件Handler
//Thread类中标识默认异常事件Handler的成员
private static UncaughtExceptionHandler defaultUncaughtHandler;
这里涉及到在哪里添加的问题,从源码中注意到,这个defaultUncaughtHandler是Thread类中一个静态的成员,所以,按道理,我们为任意一个线程设置异常处理,所有的线程都应该能共用这个异常处理器,这个是我的猜测,没有经过验证,不过没关系,有一个观点是大家都认可的:就是为主线程也就是ui线程添加异常程序器。为了在ui线程中添加异常处理Handler,我们推荐大家在Application中添加而不是在Activity中添加。Application标识着整个应用,在Android声明周期中是第一个启动的,早于任何的Activity、Service等。
/** *  */package com.example.crash.overall; import android.app.Application; /** * Description: Application *  * @author danDingCongRong * @Version 1.0.0 * @Created at 2014-7-28 14:04:06 * @Modified by [作者] on [修改日期] */public class App extends Application {     @Override    public void onCreate() {        super.onCreate();         // 开启崩溃日志,这样我们的程序就能捕获未处理的异常        CrashHandler crashHandler = CrashHandler.getInstance();        crashHandler.init(getApplicationContext());    } }
4.测试代码
package com.example.crash.activity; import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.View; import com.example.crash.R; public class MainActivity extends Activity {     @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);     }     @Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.main, menu);        return true;    }     public void testCrash(View view) {        if (1 > 0) {            throw new RuntimeException("Test Exception!!!");        }    } }
5.备注
     日志的上传属于网络相关的内容,暂且不做专门介绍

 

参考:
       1.http://blog.csdn.net/singwhatiwanna/article/details/17289479
       2.http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html
       3.http://blog.csdn.net/wangjia55/article/details/14000833
--------------------- 
作者:小屁孩2013 
来源:CSDN 
原文:https://blog.csdn.net/u010119170/article/details/38236991 
版权声明:本文为博主原创文章,转载请附上博文链接!

你可能感兴趣的:(android)