756

如何从我的 Android 应用程序中获取崩溃数据(至少是堆栈跟踪)?至少在通过电缆检索我自己的设备上工作时,但理想情况下是从我在野外运行的应用程序的任何实例中进行,以便我可以改进它并使其更加可靠。

4

30 回答 30

358

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

ACRA 是一个库,使 Android 应用程序能够自动将其崩溃报告发布到 GoogleDoc 表单。它面向 android 应用程序开发人员,以帮助他们在应用程序崩溃或出现错误行为时从应用程序中获取数据。

它很容易安装在您的应用程序中,高度可配置,并且不需要您在任何地方托管服务器脚本......报告被发送到 Google Doc 电子表格!

于 2010-05-18T08:52:09.280 回答
310

对于示例应用程序和调试目的,我使用了一个简单的解决方案,它允许我将堆栈跟踪写入设备的 sd 卡和/或将其上传到服务器。这个解决方案受到了android-remote-stacktrace项目(特别是保存到设备和上传到服务器部分)的启发,我认为它解决了 Soonil 提到的问题。它不是最佳的,但它可以工作,如果你想在生产应用程序中使用它,你可以改进它。如果您决定将堆栈跟踪上传到服务器,您可以使用 php 脚本 ( index.php) 来查看它们。如果您有兴趣,您可以在下面找到所有源代码——一个用于您的应用程序的 java 类和两个用于托管已上传堆栈跟踪的服务器的可选 php 脚本。

在上下文(例如主活动)中,调用

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");
?>
于 2009-04-16T08:00:04.437 回答
60

您也可以尝试[BugSense] 原因:垃圾邮件重定向到另一个网址。BugSense 收集并分析所有崩溃报告,并为您提供有意义且直观的报告。它是免费的,只需 1 行代码即可集成。

免责声明:我是联合创始人

于 2011-07-08T10:15:03.007 回答
45

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

Android Market 应用程序的新错误报告功能使开发人员能够接收来自用户的崩溃和冻结报告。当他们登录到他们的发布者帐户时,这些报告将可用。

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

于 2010-05-28T10:34:27.630 回答
32

可以使用 来处理这些异常Thread.setDefaultUncaughtExceptionHandler(),但这似乎与 Android 处理异常的方法相混淆。我试图使用这种性质的处理程序:

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 关闭它发生的组件,所以我在一段时间后放弃了它。

于 2009-03-18T17:24:39.133 回答
24

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

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

于 2013-06-27T06:07:07.490 回答
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上的代码进行修改, 您就有了一个很好的工作基础,可以将现场登录到您的网络服务器或 sd 卡。

于 2009-05-13T21:51:23.327 回答
21

Google Play Developers Console 实际上为您提供了来自那些已崩溃并已发送报告的应用程序的堆栈跟踪,它还有一个非常好的图表可以帮助您查看信息,请参见下面的示例:

在此处输入图像描述

于 2015-05-07T14:11:04.327 回答
18

我一直在为我的 Android 和 iOS 应用程序使用Crittercism——在 techcrunch 上听说过它们。到目前为止对他们很满意!

于 2011-10-03T00:13:54.193 回答
15

我在这里制作了自己的版本: http ://androidblogger.blogspot.com/2009/12/how-to-improve-your-application-crash.html

基本上是一样的,但是我使用邮件而不是 http 连接来发送报告,更重要的是,我在报告中添加了一些信息,例如应用程序版本、操作系统版本、手机型号或可用内存。 .

于 2009-12-15T00:25:48.267 回答
11

使用它来捕获异常详细信息:

String stackTrace = Log.getStackTraceString(exception); 

将其存储在数据库中并维护日志。

于 2012-04-30T07:31:45.827 回答
8

您还可以为它使用整个(简单)服务,而不仅仅是库。我们公司刚刚为此发布了一项服务:http: //apphance.com

它有一个简单的 .jar 库(适用于 Android),您可以在 5 分钟内添加和集成,然后该库不仅会收集崩溃信息,还会收集正在运行的应用程序的日志,它还可以让您的测试人员直接从设备报告问题 - 包括整个上下文(设备旋转,是否连接到 wifi 等等)。您可以使用一个非常漂亮且有用的 Web 面板查看日志,您可以在其中跟踪与您的应用程序的会话、崩溃、日志、统计信息等。该服务目前处于封闭 Beta 测试阶段,但您可以请求访问,我们会很快将其提供给您。

免责声明:我是 Polidea 的 CTO,也是该服务的共同创建者。

于 2011-05-29T18:03:09.273 回答
8

现在,Firebase 崩溃报告非常流行且更易于使用。请参阅以下链接了解更多信息: Firebase 崩溃报告

希望它会帮助你。

于 2017-04-20T11:01:41.373 回答
7

感谢Stackoverflow帮助我找到这个答案的资源。

您可以在电子邮件中直接找到远程 Android 崩溃报告。请记住,您必须将电子邮件放在 CustomExceptionHandler 类中。

public static String sendErrorLogsTo = "tushar.pandey@virtualxcellence.com" ;

所需步骤:

1st)在您的活动的 onCreate 中使用您的代码的这一部分。

    if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
        Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
    }   

2nd)根据我的 phpscript 使用( rrainn )的 CustomExceptionHandler 类的这个覆盖版本。

package com.vxmobilecomm.activity;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.util.Log;

public class CustomExceptionHandler implements UncaughtExceptionHandler {

    private UncaughtExceptionHandler defaultUEH;
    public static String sendErrorLogsTo = "tushar.pandey@virtualxcellence.com" ;

    Activity activity;

    public CustomExceptionHandler(Activity activity) {
        this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
        this.activity = activity;
    }

    public void uncaughtException(Thread t, Throwable e) {

        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        e.printStackTrace(printWriter);
        String stacktrace = result.toString();
        printWriter.close();
        String filename = "error" + System.nanoTime() + ".stacktrace";

        Log.e("Hi", "url != null");
        sendToServer(stacktrace, filename);

        StackTraceElement[] arr = e.getStackTrace();
        String report = e.toString() + "\n\n";
        report += "--------- Stack trace ---------\n\n";
        for (int i = 0; i < arr.length; i++) {
            report += "    " + arr[i].toString() + "\n";
        }
        report += "-------------------------------\n\n";

        report += "--------- Cause ---------\n\n";
        Throwable cause = e.getCause();
        if (cause != null) {
            report += cause.toString() + "\n\n";
            arr = cause.getStackTrace();
            for (int i = 0; i < arr.length; i++) {
                report += "    " + arr[i].toString() + "\n";
            }
        }
        report += "-------------------------------\n\n";

        defaultUEH.uncaughtException(t, e);
    }

    private void sendToServer(String stacktrace, String filename) {
        AsyncTaskClass async = new AsyncTaskClass(stacktrace, filename,
                getAppLable(activity));
        async.execute("");
    }

    public String getAppLable(Context pContext) {
        PackageManager lPackageManager = pContext.getPackageManager();
        ApplicationInfo lApplicationInfo = null;
        try {
            lApplicationInfo = lPackageManager.getApplicationInfo(
                    pContext.getApplicationInfo().packageName, 0);
        } catch (final NameNotFoundException e) {
        }
        return (String) (lApplicationInfo != null ? lPackageManager
                .getApplicationLabel(lApplicationInfo) : "Unknown");
    }

    public class AsyncTaskClass extends AsyncTask<String, String, InputStream> {
        InputStream is = null;
        String stacktrace;
        final String filename;
        String applicationName;

        AsyncTaskClass(final String stacktrace, final String filename,
                String applicationName) {
            this.applicationName = applicationName;
            this.stacktrace = stacktrace;
            this.filename = filename;
        }

        @Override
        protected InputStream doInBackground(String... params) 
        { 
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost(
                    "http://suo-yang.com/books/sendErrorLog/sendErrorLogs.php?");

            Log.i("Error", stacktrace);

            try {
                List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(
                        6);

                nameValuePairs.add(new BasicNameValuePair("data", stacktrace));
                nameValuePairs.add(new BasicNameValuePair("to",sendErrorLogsTo));
                nameValuePairs.add(new BasicNameValuePair("subject",applicationName));

                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

                HttpResponse response = httpclient.execute(httppost);

                HttpEntity entity1 = response.getEntity();

                BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(
                        entity1);

                is = bufHttpEntity.getContent();

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            return is;
        }

        @Override
        protected void onPostExecute(InputStream result) {
            super.onPostExecute(result);

            Log.e("Stream Data", getStringFromInputStream(is));
        }
    }

    // convert InputStream to String
    private static String getStringFromInputStream(InputStream is) {

        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();

        String line;
        try {

            br = new BufferedReader(new InputStreamReader(is));
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return sb.toString();

    }
}
于 2014-11-18T06:40:37.217 回答
6

Google Firebase是 Google 最新(2016 年)在您的手机上为您提供崩溃/错误数据的方式。将其包含在您的 build.gradle 文件中:

compile 'com.google.firebase:firebase-crash:9.0.0'

致命崩溃会自动记录,无需用户输入,您还可以记录非致命崩溃或其他事件,例如:

try
{

}
catch(Exception ex)
{
    FirebaseCrash.report(new Exception(ex.toString()));
}
于 2016-08-02T07:47:12.847 回答
6

有一个名为Sherlock的 android 库。它为您提供完整的崩溃报告以及设备和应用程序信息。每当发生崩溃时,它会在通知栏中显示一条通知,单击通知时,它会打开崩溃详细信息。您还可以通过电子邮件或其他共享选项与他人共享崩溃详细信息。

安装

android {
    dataBinding {
      enabled = true
    }
}

compile('com.github.ajitsing:sherlock:1.0.0@aar') {
    transitive = true
}

演示

在此处输入图像描述

于 2017-05-07T06:46:06.350 回答
5

这是非常粗暴的,但是可以在任何地方运行 logcat,所以快速而肮脏的 hack 是添加到任何 catch 块getRuntime().exec("logcat >> /sdcard/logcat.log");

于 2011-04-14T02:36:06.593 回答
5

尽管此页面上的许多答案都很有用,但它们很容易过时。AppBrain 网站汇总了统计数据,可让您找到当前最流行的崩溃报告解决方案:

Android 崩溃报告库

app brain website

您可以看到,在发布这张图片时,5.24% 的应用程序和 12.38% 的安装程序使用了 Crashlytics。

于 2017-06-26T21:40:37.473 回答
5

有一个名为 fabric 的工具,这是一个崩溃分析工具,它可以让您在应用程序实时部署和开发期间获得崩溃报告。将此工具添加到您的应用程序也很简单。当您的应用程序崩溃时,可以从您的 fabric.io 仪表板查看崩溃报告。thw 报告被自动捕获。它不会请求用户许可。他/她是否要发送错误/崩溃报告。这是完全免费的...... https://get.fabric.io/

于 2016-02-13T01:58:23.860 回答
4

我们在公司内部使用我们自己开发的系统,它为我们提供了很好的服务。这是一个将崩溃报告发送到服务器和接收报告并进行一些分析的服务器的 android 库。服务器按异常名称、堆栈跟踪、消息对异常进行分组。它有助于识别需要修复的最关键问题。我们的服务现在处于公开测试阶段,因此每个人都可以尝试。您可以在http://watchcat.co创建帐户,或者您可以使用演示访问http://watchcat.co/reports/index.php?demo看看它是如何工作的。

于 2013-03-05T10:29:38.853 回答
3

If you want answers immediately you can use logcat

$adb shell logcat -f /sdcard/logoutput.txt *:E

If there's too much junk in your log right now, try clearing it first.

$adb shell logcat -c

Then try running your app then logcat again.

于 2013-03-09T17:34:21.463 回答
3

我发现了另一个很棒的 Web 应用程序来跟踪错误报告。

https://mint.splunk.com/

配置步骤少。

  1. 使用上面的链接登录或注册和配置。一旦您完成创建应用程序,他们将提供如下配置行。
Mint.initAndStartSession(YourActivity.this, "api_key");
  1. 在应用程序的 build.gradl 中添加以下内容。
android {
...
    repositories {
        maven { url "https://mint.splunk.com/gradle/"}
    }
...
}

dependencies {
...
    compile "com.splunk.mint:mint:4.4.0"
...
}
  1. 添加我们在上面复制的代码并将其添加到每个活动中。

    Mint.initAndStartSession(YourActivity.this, "api_key");

而已。您登录并转到您的应用程序仪表板,您将获得所有错误报告。

希望它可以帮助某人。

于 2016-02-03T12:18:32.683 回答
2

对于备用崩溃报告/异常跟踪服务,请查看Raygun.io - 它有很多很好的逻辑来处理 Android 崩溃,包括将其插入应用程序时的良好用户体验(主 Activity 中的两行代码和一些粘贴到 AndroidManifest 中的 XML 行)。

当您的应用程序崩溃时,它会自动获取堆栈跟踪、硬件/软件的环境数据、用户跟踪信息、您指定的任何自定义数据等。它异步将其发布到 API,因此不会阻塞 UI 线程,并缓存它如果没有可用的网络,则写入磁盘。

免责声明:我构建了 Android 提供程序 :)

于 2013-11-05T00:50:32.077 回答
1

刚开始使用 ACRA https://github.com/ACRA/acra,使用 Google 表单作为后端,它很容易设置和使用,它是默认设置。

但是将不推荐向 Google 表单发送报告(然后删除): https: //plus.google.com/118444843928759726538/posts/GTTgsrEQdN6 https://github.com/ACRA/acra/wiki/Notice-on-Google -Form-Spreadsheet-用法

无论如何,可以定义您自己的发件人 https://github.com/ACRA/acra/wiki/AdvancedUsage#wiki-Implementing_your_own_sender 例如,您可以尝试向发件人发送电子邮件。

只需最少的努力,就可以将报告发送到 bugsense: http ://www.bugsense.com/docs/android#acra

NB bugsense 免费帐户限制为每月 500 份报告

于 2013-02-12T13:46:17.137 回答
1

派对迟到了,我支持并相信 ACRA 是最好的选择。它易于设置和配置。我创建了一份详细的指南,其中包含来自各地的输入,以使用 ACRA 获取崩溃报告,并使用 MandrillAp 将其邮寄到我的电子邮件地址。

帖子链接:https ://androidician.wordpress.com/2015/03/29/sending-crash-reports-with-acra-over-email-using-mandrill/

链接到 github 上的示例项目:https ://github.com/ayushhgoyal/AcraSample

于 2015-04-01T22:17:17.050 回答
1

我是Bugsnag的创始人之一,我们正是为此目的而设计的。Bugsnag 会自动捕获 Android 应用程序中未处理的异常并将它们发送到我们的仪表板,您可以在其中确定修复的优先级并深入了解诊断信息。

以下是选择或构建崩溃报告系统时需要考虑的一些重要事项,以及一些代码片段:

  • 自动检测未处理的异常(示例代码
  • 收集诊断数据,例如内存使用情况、设备信息等(示例代码
  • 有效地按根本原因将崩溃分组在一起
  • 允许您跟踪用户在每次崩溃之前采取的操作以帮助重现(示例代码

如果您想查看有关 Android 上的崩溃处理/报告的一些最佳实践,您可以查看 Bugsnag 的完全开源的崩溃报告库的完整源代码,随时将其拆开并在您自己的应用程序中使用!

于 2016-03-22T20:57:32.747 回答
1

Google 更改了您实际获得的崩溃报告数量。以前您只能收到手动报告的错误报告。

自上次开发者大会和Android Vitals推出以来,您还可以从已启用共享诊断数据的用户那里获得崩溃报告。

您会看到从用户选择自动共享使用情况和诊断数据的 Android 设备收集的所有崩溃信息。前两个月的数据可用。

查看崩溃和应用程序无响应 (ANR) 错误

于 2017-06-20T09:35:52.153 回答
0

Flurry 分析为您提供崩溃信息、硬件型号、Android 版本和实时应用使用统计信息。在新的 SDK 中,它们似乎提供了更详细的崩溃信息http://www.flurry.com/flurry-crash-analytics.html

于 2011-02-15T19:03:58.787 回答
0

如果您的应用程序正在被其他人下载并在远程设备上崩溃,您可能需要查看 Android 错误报告库(在此 SO 帖子中引用)。如果它只是在您自己的本地设备上,您可以使用 LogCat。即使设备在崩溃发生时没有连接到主机,连接设备并发出 adb logcat 命令将下载整个 logcat 历史记录(至少在它被缓冲的范围内,这通常是大量的日志数据,它只是不是无限的)。这些选项中的任何一个都回答了您的问题吗?如果不是,您可以尝试澄清您正在寻找的更多内容吗?

于 2011-11-03T06:28:32.743 回答
0

您可以直接在 Android Studio 中执行此操作。只需连接您的手机,运行应用程序,让它崩溃,您就可以直接在 Android Studio 中查看堆栈跟踪。

于 2018-05-01T06:05:45.767 回答