我可以使用ACRA库来通过处理未捕获的异常来管理强制关闭错误。报告可以成功发送到Google文档、电子邮件和自定义网络服务。
但是我想知道...
- 如何将报告写入文件[例如sdcard/myapp/myLog.txt]?
为什么我需要这样做..
- 我的应用用户可能在发生强制关闭时没有互联网连接...如果这样,我将错过报告,如果我将报告写入文件,那么当互联网连接可用时,我就可以将报告发送到我的服务器。
我可以使用ACRA库来通过处理未捕获的异常来管理强制关闭错误。报告可以成功发送到Google文档、电子邮件和自定义网络服务。
但是我想知道...
为什么我需要这样做..
我认为您想要实现的已经被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;
}
}
我还没有完全测试过,但你可以理解这个想法。希望能有所帮助。
我认为@Gomoku7的答案包含一些过时的代码,所以我将发布我使用的解决方案:
在onCreate()中调用此函数:
ACRA.init(this);
ACRA.getErrorReporter().setReportSender(new LocalReportSender(this));
openFileOutput()
是不可能的。 因此,只有方法send()
和构造函数LocalReportSender()
稍微有所改变。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;
}
}
@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()
)
);
}
}
我曾经使用过ACRA,但不是以这种形式(用于将日志发送到自己的服务器),所以我不确定如何做到这一点。
但在这种情况下,您不能使用其他库/ API获取整个系统日志(当然会很详细)并将其写入文件吗?
或者,您可以使用ACRA zip的代码并进行一些修改,例如使用其包中的“CrashReportData.java”或“CrashReporterDialog.java”文件,并从那里获取内容并保存到您的文件中。
我说的是它的4.2.3版本。