如何从Android应用程序获取崩溃数据?

时间:2009-03-02 07:17:27

标签: android crash stack-trace

如何从Android应用程序获取崩溃数据(至少是堆栈跟踪)?至少在使用有线电视检索我自己的设备时,最好是从我在野外运行的应用程序的任何实例开始,以便我可以改进它并使其更加稳固。

30 个答案:

答案 0 :(得分:345)

您可以尝试ACRA (Application Crash Report for Android)库:

  

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

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

答案 1 :(得分:293)

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

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

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");
?>

答案 2 :(得分:54)

您也可以尝试BugSense。 BugSense收集并分析了所有崩溃报告,并为您提供有意义的可视化报告。它是免费的,只有一行代码才能集成。

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

答案 3 :(得分:42)

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

  

Android的新错误报告功能   市场应用使开发人员能够   接收崩溃和冻结报告   他们的用户。报告将是   登录时可用   出版商帐户。

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

答案 4 :(得分:25)

可以使用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关闭它发生的组件时记录异常,所以我过了一段时间就放弃了它。

答案 5 :(得分:20)

好的,我看了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卡。

答案 6 :(得分:20)

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

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

答案 7 :(得分:18)

Google Play开发者控制台实际上为您提供了已崩溃并已发送报告的应用程序的堆栈跟踪,它还有一个非常好的图表可帮助您查看信息,请参阅下面的示例:

enter image description here

答案 8 :(得分:18)

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

答案 9 :(得分:14)

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

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

答案 10 :(得分:11)

使用它来捕获异常细节:

String stackTrace = Log.getStackTraceString(exception); 

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

答案 11 :(得分:8)

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

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

免责声明:我是Polidea的首席技术官,也是该服务的共同创建者。

答案 12 :(得分:7)

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

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

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

所需步骤:

1st)在onCreate of your activity中使用代码的这一部分。

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

2)根据我的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();

    }
}

答案 13 :(得分:5)

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

答案 14 :(得分:5)

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

希望它会对你有所帮助。

答案 15 :(得分:5)

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

Android crash reporting libraries

app brain website

您可以看到,在发布此图片时,Crashlytics用于5.24%的应用和12.38%的安装。

答案 16 :(得分:5)

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()));
}

答案 17 :(得分:5)

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

答案 18 :(得分:4)

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

<强>安装

android {
    dataBinding {
      enabled = true
    }
}

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

<强>演示

enter image description here

答案 19 :(得分:4)

我们在公司内部使用我们自己开发的系统,它为我们提供了很好的服务。它是一个安卓库,可以向服务器和服务器发送崩溃报告,接收报告并进行一些分析。服务器按例外名称,堆栈跟踪,消息对异常进行分组。它有助于确定需要修复的大多数关键问题。 我们的服务现已公开测试,所以每个人都可以尝试。您可以在http://watchcat.co创建帐户,也可以使用演示访问http://watchcat.co/reports/index.php?demo查看其工作原理。

答案 20 :(得分:3)

如果您想立即获得答案,可以使用logcat

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

如果您的日志中有太多垃圾,请先尝试清除它。

$adb shell logcat -c

然后尝试运行您的应用程序,然后再次登录。

答案 21 :(得分:3)

我找到了一个很棒的网络应用程序来跟踪错误报告。

https://mint.splunk.com/

要配置的少量步骤。

  1. 使用上述链接登录或注册和配置。完成创建应用程序后,他们将提供如下配置的行。
  2. Mint.initAndStartSession(YourActivity.this, "api_key");
    
    1. 在应用程序的build.gradl中添加以下内容。
    2. 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”);

      2. 就是这样。您登录并转到应用程序仪表板,您将收到所有错误报告。

        希望它有所帮助。

答案 22 :(得分:2)

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

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

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

答案 23 :(得分: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

答案 24 :(得分:1)

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

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

  • 自动检测未处理的例外(example code
  • 收集内存使用情况,设备信息等诊断数据(example code
  • 根据原因有效地将崩溃组合在一起
  • 允许您跟踪用户在每次崩溃前执行的操作,以帮助重现(example code

如果你想在Android上看到关于崩溃处理/报告的一些最佳实践,你可以查看完全开源的Bugsnag crash reporting library的完整源代码,随意拆开它并在你的自己的应用程序!

答案 25 :(得分:1)

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

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

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

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

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

答案 26 :(得分:0)

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

答案 27 :(得分:0)

Google改变了实际获得的崩溃报告的数量。以前您只有手动报告的错误报告。

自上次开发者大会和Android Vitals介绍以来,您还可以获得能够共享诊断数据的用户的崩溃报告。

  

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

View crashes & application not responding (ANR) errors

答案 28 :(得分:0)

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

答案 29 :(得分:0)

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