`

android 程序错误处理全局处理[转]

阅读更多
原文

本文将分析在程序出错的情况下如何收集相关的错误信息,并发送错误信息到服务器供开发者分析和调试程序。错误信息将成为您Debug的一把利刃,通过错误信息您可以最及时的掌握程序在各个系统版本和设备上的运行情况。



这种情况下,用户只有点击“强行关闭”来结束程序。当该对话框出现对用户来说是相当不友好的,本文中将会告诉您如何在程序出错时不显示该对话框。
随着OPhone设备和系统版本的增加,现在在不同设备和版本上调试程序越来越麻烦,开发者不可能购买所有的设备来逐个调试程序。如果程序在模拟器上运行正常但是到最终用户手中运行却出现了错误,这种情况下如果可以收集到程序错误堆栈信息和具体设备的信息,对开发者调试程序就有莫大的帮助了。
要收集错误信息,我们需要了解两个主要接口API的使用:android.app.Application 和java.lang.Thread.UncaughtExceptionHandler 。下面就对着两个API做简单介绍。
UncaughtExceptionHandler:线程未捕获异常控制器是用来处理未捕获异常的。如果程序出现了未捕获异常默认情况下则会出现上面所示的强行关闭对话框。在本文将实现该接口并注册为程序中的默认未捕获异常处理。这样当未捕获异常发生时,就可以做些异常处理操作,例如:收集异常信息,发送错误报告 等。
Application:在开发OPhone应用时都会和Activity打交道,而Application使用的就相对较少了。Application 在OPhone中是用来管理应用程序的全局状态的,比如载入资源文件。在应用程序启动的时候Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity或者Service。在本文将在Application中注册未捕获异常处理器。
UncaughtExceptionHandler接口实现
首先创建一个OPhone项目(项目的创建请参考OPhoneSDN上的其他文章),本文示例项目名称为:CrashReporter ;包名为:org.goodev.cr;并创建一个默认的Activity名字为:ReporterTest。然后创建CrashHandler类实现UncaughtExceptionHandler接口,并实现其函数:public void uncaughtException(Thread thread, Throwable ex)。CrashHandler类实现了错误报告的主要处理逻辑,该类代码如下(在代码中会有详细注释来解释各种处理情况):

package org.goodev.cr; 
 
Import 省略...; 
/**
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类
* 来接管程序,并记录 发送错误报告.
*
*/ 
public class CrashHandler implements UncaughtExceptionHandler { 
    /** Debug Log tag*/ 
    public static final String TAG = "CrashHandler"; 
    /** 是否开启日志输出,在Debug状态下开启,
     * 在Release状态下关闭以提示程序性能
     * */ 
    public static final boolean DEBUG = true; 
    /** 系统默认的UncaughtException处理类 */ 
    private Thread.UncaughtExceptionHandler mDefaultHandler; 
    /** CrashHandler实例 */ 
    private static CrashHandler INSTANCE; 
    /** 程序的Context对象 */ 
    private Context mContext; 
     
    /** 使用Properties来保存设备的信息和错误堆栈信息*/ 
    private Properties mDeviceCrashInfo = new Properties(); 
    private static final String VERSION_NAME = "versionName"; 
    private static final String VERSION_CODE = "versionCode"; 
    private static final String STACK_TRACE = "STACK_TRACE"; 
    /** 错误报告文件的扩展名 */ 
    private static final String CRASH_REPORTER_EXTENSION = ".cr"; 
     
    /** 保证只有一个CrashHandler实例 */ 
    private CrashHandler() {} 
    /** 获取CrashHandler实例 ,单例模式*/ 
    public static CrashHandler getInstance() { 
        if (INSTANCE == null) { 
            INSTANCE = new CrashHandler(); 
        } 
        return INSTANCE; 
    } 
 
    /**
     * 初始化,注册Context对象,
     * 获取系统默认的UncaughtException处理器,
     * 设置该CrashHandler为程序的默认处理器
     * 
     * @param ctx
     */ 
    public void init(Context ctx) { 
        mContext = ctx; 
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 
        Thread.setDefaultUncaughtExceptionHandler(this); 
    } 
 
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */ 
    @Override 
    public void uncaughtException(Thread thread, Throwable ex) { 
        if (!handleException(ex) && mDefaultHandler != null) { 
            //如果用户没有处理则让系统默认的异常处理器来处理 
            mDefaultHandler.uncaughtException(thread, ex); 
        } else { 
            //Sleep一会后结束程序 
            try { 
                Thread.sleep(3000); 
            } catch (InterruptedException e) { 
                Log.e(TAG, "Error : ", e); 
            } 
            android.os.Process.killProcess(android.os.Process.myPid()); 
            System.exit(10); 
        } 
    } 
 
    /**
     * 自定义错误处理,收集错误信息
     * 发送错误报告等操作均在此完成.
     * 开发者可以根据自己的情况来自定义异常处理逻辑
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false
     */ 
    private boolean handleException(Throwable ex) { 
        if (ex == null) { 
            return true; 
        } 
        final String msg = ex.getLocalizedMessage(); 
        //使用Toast来显示异常信息 
        new Thread() { 
            @Override 
            public void run() { 
                Looper.prepare(); 
                Toast.makeText(mContext, "程序出错啦:" + msg, Toast.LENGTH_LONG) 
                        .show(); 
                Looper.loop(); 
            } 
 
        }.start(); 
        //收集设备信息 
        collectCrashDeviceInfo(mContext); 
        //保存错误报告文件 
        String crashFileName = saveCrashInfoToFile(ex); 
        //发送错误报告到服务器 
        sendCrashReportsToServer(mContext); 
        return true; 
    } 
 
    /**
     * 在程序启动时候, 可以调用该函数来发送以前没有发送的报告
     */ 
    public void sendPreviousReportsToServer() { 
        sendCrashReportsToServer(mContext); 
    } 
 
    /**
     * 把错误报告发送给服务器,包含新产生的和以前没发送的.
     * 
     * @param ctx
     */ 
    private void sendCrashReportsToServer(Context ctx) { 
        String[] crFiles = getCrashReportFiles(ctx); 
        if (crFiles != null && crFiles.length > 0) { 
            TreeSet<String> sortedFiles = new TreeSet<String>(); 
            sortedFiles.addAll(Arrays.asList(crFiles)); 
 
            for (String fileName : sortedFiles) { 
                File cr = new File(ctx.getFilesDir(), fileName); 
                postReport(cr); 
                cr.delete();// 删除已发送的报告 
            } 
        } 
    } 
 
    private void postReport(File file) { 
        // TODO 使用HTTP Post 发送错误报告到服务器 
        // 这里不再详述,开发者可以根据OPhoneSDN上的其他网络操作 
        // 教程来提交错误报告 
    } 
 
    /**
     * 获取错误报告文件名
     * @param ctx
     * @return
     */ 
    private String[] getCrashReportFiles(Context ctx) { 
        File filesDir = ctx.getFilesDir(); 
        FilenameFilter filter = new FilenameFilter() { 
            public boolean accept(File dir, String name) { 
                return name.endsWith(CRASH_REPORTER_EXTENSION); 
            } 
        }; 
        return filesDir.list(filter); 
    } 
    /**
     * 保存错误信息到文件中
     * @param ex
     * @return
     */ 
    private String saveCrashInfoToFile(Throwable ex) { 
        Writer info = new StringWriter(); 
        PrintWriter printWriter = new PrintWriter(info); 
        ex.printStackTrace(printWriter); 
 
        Throwable cause = ex.getCause(); 
        while (cause != null) { 
            cause.printStackTrace(printWriter); 
            cause = cause.getCause(); 
        } 
 
        String result = info.toString(); 
        printWriter.close(); 
        mDeviceCrashInfo.put(STACK_TRACE, result); 
 
        try { 
            long timestamp = System.currentTimeMillis(); 
            String fileName = "crash-" + timestamp + CRASH_REPORTER_EXTENSION; 
            FileOutputStream trace = mContext.openFileOutput(fileName, 
                    Context.MODE_PRIVATE); 
            mDeviceCrashInfo.store(trace, ""); 
            trace.flush(); 
            trace.close(); 
            return fileName; 
        } catch (Exception e) { 
            Log.e(TAG, "an error occured while writing report file...", e); 
        } 
        return null; 
    } 
 
 
    /**
     * 收集程序崩溃的设备信息
     * 
     * @param ctx
     */ 
    public void collectCrashDeviceInfo(Context ctx) { 
        try { 
            PackageManager pm = ctx.getPackageManager(); 
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 
                    PackageManager.GET_ACTIVITIES); 
            if (pi != null) { 
                mDeviceCrashInfo.put(VERSION_NAME, 
                        pi.versionName == null ? "not set" : pi.versionName); 
                mDeviceCrashInfo.put(VERSION_CODE, pi.versionCode); 
            } 
        } catch (NameNotFoundException e) { 
            Log.e(TAG, "Error while collect package info", e); 
        } 
        //使用反射来收集设备信息.在Build类中包含各种设备信息, 
        //例如: 系统版本号,设备生产商 等帮助调试程序的有用信息 
//具体信息请参考后面的截图 
        Field[] fields = Build.class.getDeclaredFields(); 
        for (Field field : fields) { 
            try { 
                field.setAccessible(true); 
                mDeviceCrashInfo.put(field.getName(), field.get(null)); 
                if (DEBUG) { 
                    Log.d(TAG, field.getName() + " : " + field.get(null)); 
                } 
            } catch (Exception e) { 
                Log.e(TAG, "Error while collect crash info", e); 
            } 
 
        } 
 
    } 
 

在上面CrashHandler实现中,当错误发生的时候使用Toast显示错误信息,然后收集错误报告并保存在文件中。 发送错误报告代码请读者自己实现。在uncaughtException函数中调用了Thread.sleep(3000);来让线程停止一会是为了显示Toast信息给用户,然后Kill程序。如果你不用Toast来显示信息则可以去除该代码。除了Toast外,开发者还可以选择使用Notification来显示错误内容并让用户选择是否提交错误报告而不是自动提交。关于Notification的实现请读者参考:NotificationManager。在发送错误报道的时候,可以先检测网络是否可用,如果不可用则可以在以后网络情况可用的情况下发送。 网络监测代码如下:
view plaincopy to clipboardprint?
/**
     * 检测网络连接是否可用
     * @param ctx
     * @return true 可用; false 不可用
     */ 
    private boolean isNetworkAvailable(Context ctx) { 
        ConnectivityManager cm =  
            (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); 
        if(cm == null) { 
            return false; 
        } 
        NetworkInfo[] netinfo = cm.getAllNetworkInfo(); 
        if(netinfo == null) { 
            return false; 
        } 
        for (int i = 0; i < netinfo.length; i++) { 
            if(netinfo[i].isConnected()) { 
                return true; 
            } 
        } 
        return false; 
    } 


Application 实现
实现一个自定义Application来注册CrashHandler. 代码如下:

public class CrashApplication extends Application { 
 
    @Override 
    public void onCreate() { 
        super.onCreate(); 
        CrashHandler crashHandler = CrashHandler.getInstance(); 
        //注册crashHandler 
        crashHandler.init(getApplicationContext()); 
        //发送以前没发送的报告(可选) 
        crashHandler.sendPreviousReportsToServer(); 
    } 
     


在AndroidManifest.xml中注册
最后只要在AndroidManifest.xml中注册CrashApplication就可以了。代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
      package="org.goodev.cr" 
      android:versionCode="1" 
      android:versionName="1.0"> 
    <application android:icon="@drawable/icon" android:label="@string/app_name"  
        android:name=".CrashApplication"> 
        <activity android:name=".ReporterTest" 
                  android:label="@string/app_name"> 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
                <category android:name="android.intent.category.LAUNCHER" /> 
            </intent-filter> 
        </activity> 
    </application> 
</manifest>  

总结:通过本文示例的方式,开发者可以在程序中收集详细的崩溃信息,从而为调试程序带来便利,如果您的程序还没有该功能赶快加入吧。crashReporter.zip中包含本文使用的项目文件及资源。
  • 大小: 50.3 KB
分享到:
评论

相关推荐

    Android 自定义全局Loading页面

    在Android应用开发中,为了提供良好的用户体验,我们经常需要在数据加载、网络请求或其它耗时操作时显示全局Loading页面,以告知用户程序正在进行后台处理并提示他们稍等片刻。本篇文章将深入探讨如何在Android中...

    Android 全局异常处理

    在Android应用开发中,全局异常处理是一个至关重要的环节,它能确保应用在遇到错误时能够稳定运行,并提供良好的用户体验。本文将深入探讨Android全局异常处理的实现方式,以及如何将异常信息存储本地并上传到服务器...

    Android 捕获全局异常处理

    为了提高应用的健壮性,开发者需要学会捕获并处理全局异常。本文将详细讲解如何在Android中实现全局异常捕获,包括在崩溃前显示提示信息、保存错误日志到本地、发送错误报告邮件以及将错误信息上传至后台服务器。 ...

    Android-Android和Java的错误处理库

    在Android应用开发中,错误处理是一项至关重要的任务,它确保了程序的稳定性和用户体验。Java作为Android的主要编程语言,其错误处理机制对于开发者来说是必须掌握的。本篇将深入探讨Android与Java中的错误处理库...

    android工程全局异常处理-将未处理异常日志保存在文件中,可后续开发提交至服务器

    在Android应用开发中,异常处理是一项至关重要的任务,它能够确保程序在遇到错误时不会突然崩溃,而是能够优雅地处理并记录错误信息。本篇主要介绍如何实现一个全局的异常处理机制,以便将未处理的异常日志保存到...

    Android 全局捕获异常消息示例.rar

    android全局异常捕获,全局捕获异常消息示例,当程序发生Uncaught异常的时候,有该类来接管程序,并记录错误日志,使用系统默认的UncaughtException处理类,实现自定义错误处理,收集错误信息 发送错误报告等操作,还...

    android全局异常处理

    "android全局异常处理"这个主题关注的就是如何在整个Android应用中设置一个统一的错误处理机制,以便在发生异常时进行记录、通知用户或采取其他必要的恢复措施。 首先,我们需要理解Android应用中的异常类型。在...

    Android全局异常捕获CrashHandler

    全局异常捕获是指在整个应用程序运行过程中,无论在哪一层级或哪个线程抛出的异常,都能被一个统一的处理器捕获和处理。这样可以避免因异常导致的应用程序崩溃,提高应用的健壮性。 2. **Android异常处理流程**: ...

    Android 全局异常捕获

    当然,全局异常捕获并不能替代对每个可能出现异常的地方进行详细的错误处理。在编写具体业务逻辑时,应尽可能地预测并处理可能出现的异常情况,避免依赖全局异常捕获来处理所有的异常。全局异常捕获应当被视为最后的...

    Android捕获全局异常信息替换系统错误

    首先,我们需要创建一个自定义的应用程序(Application)类,它是整个应用程序的入口点,可以在此监听并处理全局异常。在AndroidManifest.xml文件中,将默认的Application类替换为我们自定义的类,例如`...

    android全局异常捕获

    总结起来,Android全局异常捕获是一个关键的错误处理机制,它可以帮助开发者在异常发生时采取适当的措施,防止应用崩溃,提升用户体验,同时也有利于问题的定位和修复。通过创建自定义的`Thread....

    android全局异常捕获 exception_global

    标题"android全局异常捕获 exception_global"和描述都指向了这个主题,表明我们将探讨如何在Android应用程序中实现全面的异常处理。 全局异常捕获通常涉及到创建一个全局的错误处理器,它能在应用程序的任何地方...

    android-crash, [DEPRECATED] android程序崩溃异常处理框架.zip

    一、Android程序崩溃与异常处理基础 在Android系统中,程序可能会因为各种原因如空指针异常(NullPointerException)、类型转换异常(ClassCastException)等导致崩溃。为了确保用户在遇到问题时能够得到良好的反馈...

    AndroidCrash全局崩溃异常捕获

    AndroidCrash全局崩溃异常捕获机制就是为了这一目的而设计的。它是一种技术手段,用于在应用程序出现未预期错误导致崩溃时,提供友好的用户反馈界面,同时收集设备信息和异常详情,并将这些数据发送到服务器进行分析...

    Gloading,以低耦合方式显示android应用程序的全局加载状态视图.zip

    `Gloading`就是这样一款开源项目,它旨在以低耦合的方式帮助开发者在Android应用程序中显示全局的加载状态视图。下面将详细介绍`Gloading`的工作原理、特性以及如何在项目中集成和使用。 首先,`Gloading`的设计...

    Android程序崩溃异常处理之自动发送邮件

    本教程将介绍如何实现Android程序在崩溃时自动发送邮件,以便快速获取并分析错误信息。 首先,我们需要集成一个用于发送邮件的框架包。在Java世界里,常用的库有JavaMail API和Apache Commons Net,但在Android中,...

    Android全局异常捕获

    这个处理器会捕获那些没有被其他异常处理器处理的未捕获异常,从而提供一个统一的错误处理机制。 下面我们将详细探讨如何在Android中实现全局异常捕获,以及涉及的相关技术。 1. **自定义异常处理器**: - 首先,...

    android适用与全局的网络请求dialog提示点击重新加载

    7. **异常处理**:对于可能出现的网络异常,如无网络连接、超时等,应有适当的错误处理机制,提供友好的错误信息提示。 通过以上步骤,我们可以创建一个适应于Android应用全局的网络请求对话框,它能在数据加载或...

    android全局crash捕获

    // 可能的错误处理,如重启应用或提示用户 handleException(ex); // 默认处理,结束进程 System.exit(1); } } ``` 2. **设置全局的异常处理器** 在应用启动时,我们需要将自定义的异常处理器设置为全局处理器...

    android 错误收集工具

    1. **异常捕获**:CrashHelper会在应用程序的全局异常处理器中注册,当程序发生未捕获异常时,它会优先捕获到这些异常。 2. **错误信息收集**:在捕获到异常后,CrashHelper会收集一系列关键信息,如异常类型、堆栈...

Global site tag (gtag.js) - Google Analytics