ACRA:如何将ACRA报告写入文件(SD卡中)?

8

我可以使用ACRA库来通过处理未捕获的异常来管理强制关闭错误。报告可以成功发送到Google文档、电子邮件和自定义网络服务。

但是我想知道...

  • 如何将报告写入文件[例如sdcard/myapp/myLog.txt]?

为什么我需要这样做..

  • 我的应用用户可能在发生强制关闭时没有互联网连接...如果这样,我将错过报告,如果我将报告写入文件,那么当互联网连接可用时,我就可以将报告发送到我的服务器。
5个回答

8

我认为您想要实现的已经被ACRA完成了。以下是我在abd logcat中看到的内容:

01-23 12:15:28.056: D/ACRA(614): Writing crash report file.
01-23 12:15:28.136: D/ACRA(614): Mark all pending reports as approved.
01-23 12:15:28.136: D/ACRA(614): Looking for error files in /data/data/com.ybi/files
01-23 12:15:28.136: V/ACRA(614): About to start ReportSenderWorker from #handleException
01-23 12:15:28.146: D/ACRA(614): Add user comment to null
01-23 12:15:28.146: D/ACRA(614): #checkAndSendReports - start
01-23 12:15:28.146: D/ACRA(614): Looking for error files in /data/data/com.ybi/files

ACRA首先会在您的应用程序内部存储上创建一个文件报告。然后,如果您在线且错误报告程序已正确初始化,则发送该报告。否则,这些报告将保留在数据存储中(以便稍后发送)。

我没有深入研究数据,但我目前正在开发自定义记录器。因此,如果您想要做与ACRA相同的事情,那很容易:

    ACRA.init(this);

    // a custom reporter for your very own purposes
    ErrorReporter.getInstance().setReportSender(new LocalReportSender(this));

然后:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.acra.ACRA;
import org.acra.CrashReportData;
import org.acra.ReportField;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;

import android.content.Context;

import de.akquinet.android.androlog.Log;

public class LocalReportSender implements ReportSender {

private final Map<ReportField, String> mMapping = new HashMap<ReportField, String>() ;
private FileOutputStream crashReport = null; 

public LocalReportSender(Context ctx) {
    // the destination
    try {
        crashReport = ctx.openFileOutput("crashReport", Context.MODE_WORLD_READABLE);
    } catch (FileNotFoundException e) {
        Log.e("TAG", "IO ERROR",e);
    }
}

@Override
public void send(CrashReportData report) throws ReportSenderException {

    final Map<String, String> finalReport = remap(report);

    try {
        OutputStreamWriter osw = new OutputStreamWriter(crashReport);

        Set set = finalReport.entrySet();
        Iterator i = set.iterator();

        while (i.hasNext()) {
            Map.Entry<String,String> me = (Map.Entry) i.next();
            osw.write("[" + me.getKey() + "]=" + me.getValue());
        }

        osw.flush();
        osw.close();
    } catch (IOException e) {
        Log.e("TAG", "IO ERROR",e);
    }

}

private static boolean isNull(String aString) {
    return aString == null || ACRA.NULL_VALUE.equals(aString);
}

private Map<String, String> remap(Map<ReportField, String> report) {

    ReportField[] fields = ACRA.getConfig().customReportContent();
    if (fields.length == 0) {
        fields = ACRA.DEFAULT_REPORT_FIELDS;
    }

    final Map<String, String> finalReport = new HashMap<String, String>(
            report.size());
    for (ReportField field : fields) {
        if (mMapping == null || mMapping.get(field) == null) {
            finalReport.put(field.toString(), report.get(field));
        } else {
            finalReport.put(mMapping.get(field), report.get(field));
        }
    }
    return finalReport;
}

}

我还没有完全测试过,但你可以理解这个想法。希望能有所帮助。


非常感谢用户1102206,您有没有想法,如何在重新打开应用程序后,通过在活动中点击按钮将文件中编写的这些跟踪发送到服务器?而不是自动发送报告。要求:在您完全测试之后,请更新答案。因为没有找到关于此的好帖子..所以对其他人也更有帮助.. - Jayabal
1
谢谢,Baya。关于你最初的ACRA问题,第一个点回答了你的问题:如果你的应用程序崩溃,ACRA将自行处理互联网连接的可用性,无需手动处理。关于发送报告,ACRA网站上有一些可用的东西(在这里:http://code.google.com/p/acra/source/browse/trunk/acra/src/main/java/org/acra/sender/)。上面的代码受到其中一个类的启发。当我说完全测试时,我的意思是边缘情况:设备上没有剩余空间进行报告,没有权限在SD卡上写入,ACRA配置未正确设置等。 - Gomoku7
弃用的解决方案,请参考@user2302510的答案。 - dayanruben

6

我认为@Gomoku7的答案包含一些过时的代码,所以我将发布我使用的解决方案:

在onCreate()中调用此函数:

ACRA.init(this);
ACRA.getErrorReporter().setReportSender(new LocalReportSender(this));

在这里,我基本上已经更改了代码,使用BufferedWriter,这样就可以直接写入SD卡,而使用openFileOutput()是不可能的。 因此,只有方法send()和构造函数LocalReportSender()稍微有所改变。
注意:请注意日志文件增长得非常快,因此请确保您不会因为日志文件占用用户SD卡的空间而占用MB级空间 :)
private class LocalReportSender implements ReportSender {

    private final Map<ReportField, String> mMapping = new HashMap<ReportField, String>();
    private FileWriter crashReport = null;

    public LocalReportSender(Context ctx) {
        // the destination
        File logFile = new File(Environment.getExternalStorageDirectory(), "log.txt");

        try {
            crashReport = new FileWriter(logFile, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void send(CrashReportData report) throws ReportSenderException {
        final Map<String, String> finalReport = remap(report);

        try {
            BufferedWriter buf = new BufferedWriter(crashReport);

            Set<Entry<String, String>> set = finalReport.entrySet();
            Iterator<Entry<String, String>> i = set.iterator();

            while (i.hasNext()) {
                Map.Entry<String, String> me = (Entry<String, String>) i.next();
                buf.append("[" + me.getKey() + "]=" + me.getValue());
            }

            buf.flush();
            buf.close();
        } catch (IOException e) {
            Log.e("TAG", "IO ERROR", e);
        }
    }

    private boolean isNull(String aString) {
        return aString == null || ACRAConstants.NULL_VALUE.equals(aString);
    }

    private Map<String, String> remap(Map<ReportField, String> report) {

        ReportField[] fields = ACRA.getConfig().customReportContent();
        if (fields.length == 0) {
            fields = ACRAConstants.DEFAULT_REPORT_FIELDS;
        }

        final Map<String, String> finalReport = new HashMap<String, String>(
                report.size());
        for (ReportField field : fields) {
            if (mMapping == null || mMapping.get(field) == null) {
                finalReport.put(field.toString(), report.get(field));
            } else {
                finalReport.put(mMapping.get(field), report.get(field));
            }
        }
        return finalReport;
    }

}

谢谢!这对我有用,从这里复制了一个代码块,并且使用Eclipse修复了缺少的导入。生成的文件保存在sd卡的根目录下,大小为27k。强烈推荐使用! - Stephen Hosking

1

@Gomoku7和@user1071762的答案包含一些已弃用的代码,它们非常古老,而且ACRA已经更新了很多,我想我会发布一个基于以前答案创建的更新解决方案,因为我无法让它们工作。

这是使用ACRA 5.9.5,并已在Android API 21到32上进行了测试,它还使用标准报告输出方法

依赖项

dependencies {
    def acraVersion = '5.9.5'
    implementation "ch.acra:acra-toast:$acraVersion"
    annotationProcessor("com.google.auto.service:auto-service:1.0.1")
    compileOnly("com.google.auto.service:auto-service-annotations:1.0.1")
}

LocalReportSender.java

import android.content.Context;
import android.os.Environment;
import android.util.Log;


import androidx.annotation.NonNull;

import java.io.File;
import java.io.FileWriter;

import com.google.auto.service.AutoService;
import org.acra.config.CoreConfiguration;
import org.acra.data.CrashReportData;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;
import org.acra.sender.ReportSenderFactory;
import org.jetbrains.annotations.NotNull;

public class LocalReportSender implements ReportSender {
   CoreConfiguration config;

   public LocalReportSender(CoreConfiguration coreConfiguration) {
      config = coreConfiguration;
   }

   @Override
   public void send(@NotNull Context context, @NotNull CrashReportData errorContent) 
        throws ReportSenderException {
      // the destination
      // This usually appear as:-
      // Internal shared storage\Android\data\{packagename}\files\Documents
      // on USB connection or Google files App
      File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);

      String state = Environment.getExternalStorageState();

      File logFile;
      if(Environment.MEDIA_MOUNTED.equals(state)) {
         logFile = new File(dir, "crash_report.txt");
      } else {
         // backup if external storage is not available
         logFile = new File(context.getCacheDir(),"crash_report.txt");
      }

      try {
         // Use the core ReportFormat configuration
         String reportText = config.getReportFormat()
           .toFormattedString(errorContent,
             config.getReportContent(), "\n", "\n\t", false);

         // Overwrite last report
         FileWriter writer = new FileWriter(logFile, false);
         writer.append(reportText);
         writer.flush();
         writer.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
      Log.d("[LocalReportSender]", "Report Saved");
   }

   @AutoService(ReportSenderFactory.class)
   public static class LocalReportFactory implements ReportSenderFactory {
      @NotNull
      @Override
      public ReportSender create(@NotNull Context context,
         @NotNull CoreConfiguration coreConfiguration) {
         Log.d("[LocalReportSender]", "LocalReportSender created!");
         return new LocalReportSender(coreConfiguration);
      }

      @Override
      public boolean enabled(@NonNull CoreConfiguration coreConfig) {
         Log.d("[LocalReportSender]", "LocalReportSender enabled!");
         return true;
      }
   }
}

LocalReportSender的关键点是使用@AutoService注解将其加载到ACRA配置中。

应用程序类

import android.app.Application;
import android.content.Context;

import org.acra.ACRA;
import org.acra.config.CoreConfigurationBuilder;
import org.acra.config.ToastConfigurationBuilder;
import org.acra.data.StringFormat;

public class MyApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        ACRA.init(this, new CoreConfigurationBuilder()
                //core configuration:
                .withBuildConfigClass(BuildConfig.class)
                .withReportFormat(StringFormat.KEY_VALUE_LIST)
                .withPluginConfigurations(
                        //each plugin you chose above can be configured
                        //with its builder like this:
                        new ToastConfigurationBuilder()
                                //required
                                .withText(getString(R.string.crash_toast_text))
                                .build()
                )
        );
    }
}


0

上述解决方案完美地运作。可能只有一件事情,如果使用文件浏览器无法看到该文件,请尝试添加意图广播到Intent.ACTION_MEDIA_SCANNER_SCAN_FILE

查看此链接


0

我曾经使用过ACRA,但不是以这种形式(用于将日志发送到自己的服务器),所以我不确定如何做到这一点。
但在这种情况下,您不能使用其他库/ API获取整个系统日志(当然会很详细)并将其写入文件吗?

或者,您可以使用ACRA zip的代码并进行一些修改,例如使用其包中的“CrashReportData.java”或“CrashReporterDialog.java”文件,并从那里获取内容并保存到您的文件中。

我说的是它的4.2.3版本。


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