如何在我的Android应用中获取崩溃数据?

767

我怎样才能从我的Android应用程序获取崩溃数据(至少是堆栈跟踪)?最好能够通过电缆从自己的设备检索,但更理想的是能够从任何正在运行的应用程序实例中检索,这样我就可以改善它并使其更加稳定。


10
好的,这是需要翻译的内容:请查看这个链接:https://github.com/tomquist/Android-Error-Reporter。 - Tom
我看到这将报告发送到远程服务器。它能否同时将异常记录到本地文件中? - user600838
1
Android 应用程序崩溃报告 http://code.google.com/p/acra/ - gdonald
这个看起来更加健壮,如果在所有重试之后报告上传失败了,能否将其记录到文件或sqlite数据库中? - JPM
你尝试过 Firebase Crashlytics 吗? - mmdreza baqalpour
30个回答

360

您可以尝试使用ACRA (Android 应用程序崩溃报告)库:

ACRA 是一个库,使 Android 应用程序能够自动将其崩溃报告发布到 Google 表单中。它面向的是 Android 应用程序开发者,帮助他们在应用程序崩溃或行为异常时获取数据。

您可以轻松地在您的应用程序中安装 ACRA 库,它高度可配置,并不需要您在任何地方托管服务器脚本...... 报告会发送到 Google 文档电子表格中!


8
这个设置和使用都很容易。推荐在预市场使用,甚至可能在之后也适用。 - mxcl
8
Acra的最大优点是可以利用Google APIs轻松分析和可视化数据。请参阅http://jberkel.github.com/sms-backup-plus/acra-analysis以了解如何进行操作的示例。 - Jan Berkel
3
对我来说似乎很不稳定。ACRA 本身崩溃了,并发送了有关自身而非相关应用程序崩溃的崩溃报告。-1 - Sandor
19
不再支持以 Google Docs 作为后端。 - eliocs
1
如何设置ACRA的好教程:https://www.toptal.com/android/automated-android-crash-reports-with-acra-and-cloudant - Ryan Pfister
显示剩余4条评论

313
为了进行样例应用和调试,我使用了一种简单的解决方案,允许我将堆栈跟踪写入设备的SD卡并/或上传到服务器。这个解决方案受到项目android-remote-stacktrace(特别是保存到设备和上传到服务器部分)的启发,我认为它解决了Soonil提到的问题。虽然不是最佳解决方案,但它可以工作,并且您可以在生产应用程序中使用时对其进行改进。如果您决定将堆栈跟踪上传到服务器,则可以使用一个php脚本(index.php)来查看它们。如果您感兴趣,可以在下面找到所有源代码 - 一个Java类用于您的应用程序以及两个可选的php脚本用于托管上传的堆栈跟踪的服务器。
在上下文中(例如主Activity),调用:
if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
    Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(
            "/sdcard/<desired_local_path>", "http://<desired_url>/upload.php"));
}

CustomExceptionHandler

public class CustomExceptionHandler implements UncaughtExceptionHandler {

    private UncaughtExceptionHandler defaultUEH;

    private String localPath;

    private String url;

    /* 
     * if any of the parameters is null, the respective functionality 
     * will not be used 
     */
    public CustomExceptionHandler(String localPath, String url) {
        this.localPath = localPath;
        this.url = url;
        this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
    }

    public void uncaughtException(Thread t, Throwable e) {
        String timestamp = TimestampFormatter.getInstance().getTimestamp();
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        e.printStackTrace(printWriter);
        String stacktrace = result.toString();
        printWriter.close();
        String filename = timestamp + ".stacktrace";

        if (localPath != null) {
            writeToFile(stacktrace, filename);
        }
        if (url != null) {
            sendToServer(stacktrace, filename);
        }

        defaultUEH.uncaughtException(t, e);
    }

    private void writeToFile(String stacktrace, String filename) {
        try {
            BufferedWriter bos = new BufferedWriter(new FileWriter(
                    localPath + "/" + filename));
            bos.write(stacktrace);
            bos.flush();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendToServer(String stacktrace, String filename) {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("filename", filename));
        nvps.add(new BasicNameValuePair("stacktrace", stacktrace));
        try {
            httpPost.setEntity(
                    new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            httpClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

upload.php

<?php
    $filename = isset($_POST['filename']) ? $_POST['filename'] : "";
    $message = isset($_POST['stacktrace']) ? $_POST['stacktrace'] : "";
    if (!ereg('^[-a-zA-Z0-9_. ]+$', $filename) || $message == ""){
        die("This script is used to log debug data. Please send the "
                . "logging message and a filename as POST variables.");
    }
    file_put_contents($filename, $message . "\n", FILE_APPEND);
?>

index.php

<?php
    $myDirectory = opendir(".");
    while($entryName = readdir($myDirectory)) {
        $dirArray[] = $entryName;
    }
    closedir($myDirectory);
    $indexCount = count($dirArray);
    sort($dirArray);
    print("<TABLE border=1 cellpadding=5 cellspacing=0 \n");
    print("<TR><TH>Filename</TH><TH>Filetype</th><th>Filesize</TH></TR>\n");
    for($index=0; $index < $indexCount; $index++) {
        if ((substr("$dirArray[$index]", 0, 1) != ".") 
                && (strrpos("$dirArray[$index]", ".stacktrace") != false)){ 
            print("<TR><TD>");
            print("<a href=\"$dirArray[$index]\">$dirArray[$index]</a>");
            print("</TD><TD>");
            print(filetype($dirArray[$index]));
            print("</TD><TD>");
            print(filesize($dirArray[$index]));
            print("</TD></TR>\n");
        }
    }
    print("</TABLE>\n");
?>

9
我认为这可能会在某些州/国家引起法律问题。 - setzamora
5
注意:如果您的目标是Honeycomb或更高版本,则现在必须将HttpPost httpPost = new HttpPost(url);放在异步任务(或处理程序...单独的线程)中。 - Bryan Denny
4
@Joset,是什么样的法律问题,为什么? - varevarao
2
@varevarao 由于使用此代码的“发送到服务器”功能发送敏感设备信息而未经用户同意,可能会涉及法律问题。 - caw
2
默认异常处理程序似乎会在Activity重新创建时持续存在。我更新了答案,只有在没有设置自定义处理程序时才设置默认处理程序。如果没有它,每个处理程序都会保留并调用先前的处理程序,一直到原始处理程序。这会导致重复记录问题以及内存泄漏问题。 - Dave McClelland
显示剩余2条评论

60
您也可以尝试使用 [BugSense] 原因:垃圾信息重定向到另一个网址。 BugSense 收集并分析所有崩溃报告,并提供有意义且可视化的报告。它是免费的,只需要1行代码即可集成。
免责声明:我是联合创始人。

4
我已经尝试过BugSense了,非常棒。界面简洁、清新,速度也很快。没有无意义的特性臃肿。易于查看最频繁的崩溃情况,并进行调用栈分析。 - vidstige
我们最近不仅仅是进行崩溃报告,但这不是谈论新功能等的合适场所。 - PanosJee
尝试过了。虽然堆栈跟踪不太完整,但错误已经解决了,实时崩溃数据没有传递过来。调试模式也没用。我从Application类中走了一遍初始化路线(这对大多数情况都是有意义的)。BugSense有一个惊人的仪表板,可惜由于某种原因崩溃报告不起作用,符号化也不在免费层中。花了我5分钟安装GetSentry,它可以直接使用这个通用客户端为Android工作: https://github.com/joshdholtz/Sentry-Android - albertpeiro
3
BugSense很简单易于集成,但是太昂贵了。我们可以使用Flurry分析和Parse实现相同的错误感知功能。这两个工具都是免费且易于集成。 - venkat

45

在 Android 2.2 版本中,现在可以自动从 Android 市场应用程序获取崩溃报告:

为 Android 市场应用程序提供了新的错误报告功能,使开发人员能够从用户处接收崩溃和冻结报告。当开发者登录其发布者帐户时,这些报告将可用。

http://developer.android.com/sdk/android-2.2-highlights.html


1
@Janusz 你确定吗?Nexus One已经发布了Froyo版本,这还不包括运行Froyo一段时间的Google员工。 - pupeno
我不知道报告是否已经发送给了谷歌。该报告有些奇怪,因为谷歌显示了一个用于提交视频的用户界面,并且这必须是操作系统的更改,而不仅仅是市场方面的变化。但是报告中指出:平台1每周提交了报告,而该Android设备不应该运行Froyo系统。 - Janusz
1
“报告”按钮代码已经存在AOSP中一段时间了,Romain Guy在几个月前模糊地在这里回答了一个关于它的问题。 - Christopher Orr
请参阅http://android-developers.blogspot.com.au/2010/05/google-feedback-for-android.html。 - poolie
当前链接:http://android-developers.blogspot.com/2010/05/google-feedback-for-android.html - Mikko Rantalainen
显示剩余2条评论

32

使用 Thread.setDefaultUncaughtExceptionHandler() 可以处理这些异常,但是这似乎会干扰 Android 处理异常的方法。我尝试使用了这样的一个 handler:

private class ExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable ex){
        Log.e(Constants.TAG, "uncaught_exception_handler: uncaught exception in thread " + thread.getName(), ex);

        //hack to rethrow unchecked exceptions
        if(ex instanceof RuntimeException)
            throw (RuntimeException)ex;
        if(ex instanceof Error)
            throw (Error)ex;

        //this should really never happen
        Log.e(Constants.TAG, "uncaught_exception handler: unable to rethrow checked exception");
    }
}
然而,即使重新抛出异常,我仍然无法获得所需的行为,即在允许Android关闭发生异常的组件的同时记录异常,因此过了一段时间后我放弃尝试了。

为什么你只重新抛出未经检查的异常?在我看来,你应该重新抛出所有异常。 - Tyler
似乎有人成功采用了您的方法:http://jyro.blogspot.com/2009/09/crash-report-for-android-app.html - Tyler
1
关键是获取先前的默认UncaughtExceptionHandler并在报告异常完成后将异常处理到该对象。 - Tom
Constants.TAG 是 Android 框架的一部分吗?第一次看到它,似乎找不到它。 - Haroun Hajem
1
@HarounHajem 我们需要定义自己的。 - KYHSGeekCode

25

我看到这个问题太老了,希望我的答案对其他遇到相同问题的人有所帮助...

试试Crashlytics吧。它将深入了解所有设备上应用程序的崩溃情况,并通过电子邮件向您发送通知。而且最好的部分是完全免费使用。


22

好的,我查看了rrainn和Soonil提供的示例,并找到了一种解决方案,它不会破坏错误处理。

我修改了CustomExceptionHandler,使其存储来自我们关联新异常处理程序的线程的原始UncaughtExceptionHandler。在新的“uncaughtException”方法结束时,我只是使用存储的UncaughtExceptionHandler调用旧函数。

在DefaultExceptionHandler类中,您需要像这样的内容:

public class DefaultExceptionHandler implements UncaughtExceptionHandler{
  private UncaughtExceptionHandler mDefaultExceptionHandler;

  //constructor
  public DefaultExceptionHandler(UncaughtExceptionHandler pDefaultExceptionHandler)
  {
       mDefaultExceptionHandler= pDefaultExceptionHandler;
  }
  public void uncaughtException(Thread t, Throwable e) {       
        //do some action like writing to file or upload somewhere         

        //call original handler  
        mStandardEH.uncaughtException(t, e);        

        // cleanup, don't know if really required
        t.getThreadGroup().destroy();
  }
}

通过对代码进行修改,您可以在http://code.google.com/p/android-remote-stacktrace上获得一个良好的工作基础,用于将日志记录到您的Web服务器或SD卡中。


21

谷歌开发者控制台实际上为您提供了那些崩溃并发送报告的应用程序的堆栈跟踪,它还有非常好的图表来帮助您查看信息,如下所示:

输入图像描述


2
如果您的应用程序不在Play商店中,那么如何让用户发送报告?或者说,如果只是为了测试目的,您能否这样做? - Lion789
你知道是否有任何公开其崩溃报告的开源Android项目吗? - Justin Civi
1
请注意,这将仅显示用户选择报告的崩溃和ANR。 - sojin
确实,当你拥有庞大的用户群时,所有崩溃都会被报告,而这里报告的崩溃和ANR将是最重要的,因为人们会多次报告它们,这样你就可以按照优先级过滤和修复它们(更多的报告=更重要的错误),而没有报告的则不那么关键。 - Eefret

18

我一直在使用Crittercism来监控我的Android和iOS应用程序,是在TechCrunch上听说过的。目前为止感觉还不错!


请注意:来自 NDK 方面的崩溃报告不属于基本计划,应包含在“扩展崩溃报告”中。请参见:http://www.crittercism.com/pricing/ - ahash
1
我刚刚在Twitter的Fabric.io Crashlytics上测试了NDK报告...非常不错(在第一天使用时),当我发布应用时,需要额外的步骤将映射放入到他们的系统中,以便堆栈跟踪看起来很好,但它成功地捕捉了我用于测试的NDK错误(在c++中有策略性地加入了“abort();”调用),并提供了一个堆栈跟踪以供测试。 - Hunter-Orionnoir

15

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接