I can use ACRA library to manage force close error by handling uncaught exception. The report can sent to google doc., email and custom web service successfully..
But what I want..
why I want this..
I think what you want to achieve is already done by ACRA. Here's what I see in my 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
First thing that ACRA does is creating a report on a file on the inner storage of your app. Then if you're online and the errorreporter is correctly initialized, it sends the report. Otherwise, the reports are kept in the data storage (for a later sending).
I didn't look into the data but I'm currently working on a custom logger. So if you want to do the same things than ACRA, it's easy :
ACRA.init(this);
// a custom reporter for your very own purposes
ErrorReporter.getInstance().setReportSender(new LocalReportSender(this));
And then :
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;
}
}
I didn't fully tested it yet but you get the idea. Hope it helps.
I guess the answer from @Gomoku7 contains some deprecated code so I'll just post the solution that I used:
Call this in onCreate():
ACRA.init(this);
ACRA.getErrorReporter().setReportSender(new LocalReportSender(this));
Here I have basically changed the code to use BufferedWriter so that I could write directly to SD card which was not possible with openFileOutput()
. Therefore only method send()
and constructor LocalReportSender()
are slightly changed.
Note: Be aware that the logfile grows quite quickly so be sure you don't take up MBs of space of your user's SDcard because of a log file :)
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;
}
}
The answer from @Gomoku7 and @user1071762 contains some deprecated code so and are very old and ACRA has been updated at lot that I thought I'll just post the an updated solution that I created based on the previous answers as I could not get them to work.
This is using ACRA 5.9.5 and has been tested on Android API 21 to 32, it also uses the standard report output methods
Dependencies
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;
}
}
}
The key point on the LocalReportSender to get it loaded in to the ACRA config is the @AutoService
annotation
Application class
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()
)
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With