Android崩溃是开发中不可避免的一件事,考虑不够周全的代码、糟糕的网络环境、让人头疼的碎片化问题都可能导致crash,线上版本crash严重影响用户体验,所以crash的捕获和收集对我们开发人员很重要。

〇、Exception的分类及捕获

Java的异常可以分为两类:

  • UnChecked Exception
  • Checked Exception

所有RuntimeException类及其子类的实例被称为Runtime异常,即UnChecked Exception,

不是RuntimeException类及其子类的异常实例则被称为Checked Exception。Checked异常又称为编译时异常,即在编译阶段被处理的异常。编译器会强制程序处理所有的Checked异常,也就是用try…catch显式的捕获并处理,因为Java认为这类异常都是可以被处理(修复)的。在Java API文档中,方法说明时,都会添加是否throw某个exception,这个exception就是Checked异常。如果没有try…catch这个异常,则编译出错,错误提示类似于“Unhandled exception type xxxxx ...”and so on .

一、实现Thread.UncaughtExceptionHandler

UnChecked异常发生时,由于没有相应的try…catch处理该异常对象,所以Java运行环境将会终止,程序将退出,也就是我们所说的Crash。Java API提供了一个全局异常捕获处理器,Android应用在Java层捕获Crash依赖的就是Thread.UncaughtExceptionHandler处理器接口,通常我们只需实现这个接口,并重写其中的uncaughtException方法,在该方法中可以读取Crash的堆栈信息。

public class CrashManager implements Thread.UncaughtExceptionHandler {    private Thread.UncaughtExceptionHandler mDefaultHandler;    private Map<String, String> infos;    private MyApplication application;    public CrashManager(MyApplication application){        //获取系统默认的UncaughtExceptionHandler        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();        this.application = application;    }    private boolean handleException(final Throwable exc){        if (exc == null) {            return false;        }        new Thread(new Runnable() {            @Override            public void run() {                Looper.prepare();                Log.i("Urmytch","崩溃正在写入日志");                flushBufferedUrlsAndReturn();                //处理崩溃                collectDeviceAndUserInfo(application);                writeCrash(exc);                Looper.loop();            }        }).start();        return true;    }    /**     * 把未存盘的url和返回数据写入日志文件     */    private void flushBufferedUrlsAndReturn(){        //TODO 可以在请求网络时把url和返回xml或json数据缓存在队列中,崩溃时先写入以便查明原因    }    /**     * 采集设备和用户信息     * @param context 上下文     */    private void collectDeviceAndUserInfo(Context context){        PackageManager pm = context.getPackageManager();        infos = new HashMap<String, String>();        try {            PackageInfo pi = pm.getPackageInfo(context.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);                infos.put("crashTime",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));            }        } catch (PackageManager.NameNotFoundException e) {                Log.e("Urmytch",e.getMessage());        }        Field[] fields = Build.class.getDeclaredFields();        try {            for (Field field : fields) {                field.setAccessible(true);                infos.put(field.getName(), field.get(null).toString());            }        } catch (IllegalAccessException e) {            Log.e("Urmytch",e.getMessage());        }    }    /**     * 采集崩溃原因     * @param exc 异常     */    private void writeCrash(Throwable exc){        StringBuffer sb = new StringBuffer();        sb.append("------------------crash----------------------");        sb.append("\r\n");        for (Map.Entry<String,String> entry : infos.entrySet()) {            String key = entry.getKey();            String value = entry.getValue();            sb.append(key+"="+value+"\r\n");        }        Writer writer = new StringWriter();        PrintWriter pw = new PrintWriter(writer);        exc.printStackTrace(pw);        Throwable excCause = exc.getCause();        while (excCause != null) {            excCause.printStackTrace(pw);            excCause = excCause.getCause();        }        pw.close();        String result = writer.toString();        sb.append(result);        sb.append("\r\n");        sb.append("-------------------end-----------------------");        sb.append("\r\n");        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))        {            String sdcardPath = Environment.getExternalStorageDirectory().getPath();            String filePath = sdcardPath + "//Urmytch/crash/";            writeLog(sb.toString(), filePath);        }    }    /**     *     * @param log 文件内容     * @param name 文件路径     * @return 返回写入的文件路径     * 写入Log信息的方法,写入到SD卡里面     */    private String writeLog(String log, String name)    {        String filename = name + "mycrash"+ ".log";        File file =new File(filename);        if(!file.getParentFile().exists()){            Log.i("Urmytch","新建文件");            file.getParentFile().mkdirs();        }        if (file != null && file.exists() && file.length() + log.length() >= 64 * 1024) {            //控制日志文件大小            file.delete();        }        try        {            file.createNewFile();            FileWriter fw=new FileWriter(file,true);            BufferedWriter bw = new BufferedWriter(fw);            //写入相关Log到文件            bw.write(log);            bw.newLine();            bw.close();            fw.close();            return filename;        }        catch(IOException e)        {            Log.w("Urmytch",e.getMessage());            return null;        }    }    @Override    public void uncaughtException(Thread thread, Throwable exc) {        if(!handleException(exc) && mDefaultHandler != null){            //如果用户没有处理则让系统默认的异常处理器来处理            mDefaultHandler.uncaughtException(thread, exc);        }else{            try{                Thread.sleep(2000);            }catch (InterruptedException e){                Log.w("Urmytch",e.getMessage());            }            Intent intent = new Intent(application.getApplicationContext(), MainActivity.class);            PendingIntent restartIntent = PendingIntent.getActivity(                    application.getApplicationContext(), 0, intent,                    0);            //退出程序            AlarmManager mgr = (AlarmManager)application.getSystemService(Context.ALARM_SERVICE);            //1秒后重启应用            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,                    restartIntent);            android.os.Process.killProcess(android.os.Process.myPid());        }    }}

二、在Application中注册

public class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        CrashManager crashHandler = new CrashManager(this);        Thread.setDefaultUncaughtExceptionHandler(crashHandler);    }}

三、看下效果

点击button会直接crash掉




四、与友盟错误统计是否会存在冲突

  • 友盟是这样说的:

开发者自己使用UncaughtExceptionHandler在程序中添加了全局的异常捕捉时,如果是开发者的先注册友盟的后注册,友盟不会覆盖开发者的,但是如果友盟先注册,开发者注册的可能会覆盖友盟的。如果您不需要错误统计,可以通过MobclickAgent.setCatchUncaughtExceptions(false);关闭,如果开发者需要自己上传错误,友盟也提供相应的方法:public static void reportError(Context context, String error) //或public static void reportError(Context context, Throwable e)。

  • 经验证本demo中并未冲突,本地捕获并记录crash信息(重启应用后友盟才会提交崩溃数据)后友盟依然记录到了崩溃

更多相关文章

  1. Android(安卓)NDK编程之Android.mk和Application.mk
  2. Android(安卓)NDK开发之旅(1): Eclipse中NDK环境搭建与JNI开发流
  3. Android基础入门教程——7.1.3 Android(安卓)HTTP请求方式-HttpU
  4. android 自定义权限 permission
  5. Android(安卓)破解apk文件
  6. Android(安卓)Service学习之本地服务
  7. 如何用Android(安卓)源码生成APK签名文件
  8. Android中的文件存储数据方式 .
  9. Android(安卓)Studio 引入编写好的.so库

随机推荐

  1. android回到桌面的方法
  2. Android(安卓)Studio如何直接安装release
  3. Fragment
  4. Android开发资料推荐之20个Android游戏源
  5. 全志A20 Android(安卓)源码编译
  6. Android(安卓)databinding笔记
  7. Android(安卓)近百个项目的源代码,覆盖And
  8. Android(安卓)对话框Activity去除白色边
  9. [android] No resource found that match
  10. AndroidManifest.xml Activity 属性说明