Wednesday, 2 July 2014

Make your own Crash Reporter


Whenever app crashes it's better to get the information about the crash, so that we can fix it and give user better experience. After googling and taking reference I write down the below code.




Download Source Code Download

Project hierarchy

1) Make UtilFunctions.java class in this we will write a function that reads Log of the project, which is sent in the mail along with crash detail so that we can know what had happened before crash.

UtilFunctions.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import android.content.Context;

public class UtilFunctions {

      public static String readLog(Context context) {
            String command = "logcat -d -v time";
            Process mLogcatProc = null;
            BufferedReader reader = null;
            StringBuilder log = null;
            try {
                  mLogcatProc = Runtime.getRuntime().exec(command);
                  reader = new BufferedReader(new InputStreamReader(mLogcatProc.getInputStream()));
                  String line;
                  log = new StringBuilder();
                  String separator = System.getProperty("line.separator");
                  while ((line = reader.readLine()) != null) {
                        log.append(line);
                        log.append(separator);
                  }
            } catch (IOException e) {
            } finally {
                  if (reader != null) {
                        try {
                              reader.close();
                        } catch (IOException e) {}
                  }
            }
            return log.toString();
      }
}

2) Now make DeviceInfo.java class this will have functions which reads device information, which is sent in the mail along with crash detail(like version name, package name, model, display etc).

DeviceInfo.java

import java.io.File;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.os.StatFs;

public class DeviceInfo {
      String VersionName;
      String PackageName;
      String FilePath;
      String PhoneModel;
      String AndroidVersion;
      String Board;
      String Brand;
      String Device;
      String Display;
      String FingerPrint;
      String Host;
      String ID;
      String Model;
      String Product;
      String Tags;
      long Time;
      String Type;
      String User;
      Context context;
     
      public DeviceInfo(Context context){
            this.context = context;
      }
     
      void collectInformation(Context context) {
            PackageManager pm = context.getPackageManager();
            try {
                  PackageInfo pi;
                  pi = pm.getPackageInfo(context.getPackageName(), 0);
                  VersionName = pi.versionName;
                  PackageName = pi.packageName;
                  FilePath = context.getFilesDir().getAbsolutePath();
                  PhoneModel = android.os.Build.MODEL;
                  AndroidVersion = android.os.Build.VERSION.RELEASE;
                  Board = android.os.Build.BOARD;
                  Brand = android.os.Build.BRAND;
                  Device = android.os.Build.DEVICE;
                  Display = android.os.Build.DISPLAY;
                  FingerPrint = android.os.Build.FINGERPRINT;
                  Host = android.os.Build.HOST;
                  ID = android.os.Build.ID;
                  Model = android.os.Build.MODEL;
                  Product = android.os.Build.PRODUCT;
                  Tags = android.os.Build.TAGS;
                  Time = android.os.Build.TIME;
                  Type = android.os.Build.TYPE;
                  User = android.os.Build.USER;

            } catch (NameNotFoundException e) {
                  e.printStackTrace();
            }
      }
     
      public long getAvailableInternalMemorySize() {
            File path = Environment.getDataDirectory();
            StatFs stat = new StatFs(path.getPath());
            long blockSize = stat.getBlockSize();
            long availableBlocks = stat.getAvailableBlocks();
            return availableBlocks * blockSize;
      }

      public long getTotalInternalMemorySize() {
            File path = Environment.getDataDirectory();
            StatFs stat = new StatFs(path.getPath());
            long blockSize = stat.getBlockSize();
            long totalBlocks = stat.getBlockCount();
            return totalBlocks * blockSize;
      }
     
      public String CreateInformationString() {
            collectInformation(context);
            String separator = System.getProperty("line.separator");
            StringBuilder sb = new StringBuilder();
            sb.append("Version : " + VersionName + separator);
            sb.append("Package : " + PackageName + separator + separator);
            sb.append("FilePath : " + FilePath + separator);
            sb.append("Phone Model : " + PhoneModel + separator);
            sb.append("Android Version : " + AndroidVersion + separator);
            sb.append("Board : " + Board + separator);
            sb.append("Brand : " + Brand + separator);
            sb.append("Device : " + Device + separator);
            sb.append("Display : " + Display + separator);
            sb.append("Finger Print : " + FingerPrint + separator);
            sb.append("Host : " + Host + separator);
            sb.append("ID : " + ID + separator);
            sb.append("Model : " + Model + separator);
            sb.append("Product : " + Product + separator);
            sb.append("Tags : " + Tags + separator);
            sb.append("Time : " + Time + separator);
            sb.append("Type : " + Type + separator);
            sb.append("User : " + User + separator);
            sb.append("Total Internal memory : " + getTotalInternalMemorySize() + separator);
            sb.append("Available Internal memory : " + getAvailableInternalMemorySize() + separator);
            return sb.toString();
      }
     
      public String getFilePath(){
            return context.getFilesDir().getAbsolutePath();
      }
}

3) And lastly make CrashReporter.java class which will monitor crash.

CrashReporter.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Date;
import android.content.Context;
import android.content.Intent;

public class CrashReporter implements Thread.UncaughtExceptionHandler {
      private Thread.UncaughtExceptionHandler perviousHandler;
      private static CrashReporter instance;
      private Context mContext;
      String filePath;
      DeviceInfo deviceInfo;
      String separator;
    
      public static CrashReporter getInstance() {
            if (instance == null)
                  instance = new CrashReporter();
            return instance;
      }

      public void init(Context context) {
            perviousHandler = Thread.getDefaultUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(this);
            deviceInfo = new DeviceInfo(context);
            filePath = deviceInfo.getFilePath();
            separator = System.getProperty("line.separator");
            mContext = context;
      }

      public void uncaughtException(Thread t, Throwable e) {
            StringBuilder infoString = new StringBuilder();

            String time = new Date().toString();
            String deviceInformation = deviceInfo.CreateInformationString();
            String stackTrace = readStackTrace(e);
            String stackCause = getStackCause(e);
            String logcat = UtilFunctions.readLog(mContext);

            infoString.append("****  Start of current Report ***");
            infoString.append("Error Report collected on : " + time + separator);
            infoString.append("Informations :" + separator + "=>" + separator + deviceInformation + separator);
            infoString.append("StackTrace : " + separator + "=>" + separator + stackTrace + separator);
            infoString.append("StackCause : " + separator + "=>" + separator + stackCause + separator);
            infoString.append("Logcat : " + separator + "=>" + separator + logcat + separator);
            infoString.append("****  End of current Report ***");
            saveAsFile(infoString.toString());
            perviousHandler.uncaughtException(t, e);
      }
     
      private String readStackTrace(Throwable e) {
            final Writer result = new StringWriter();
            final PrintWriter printWriter = new PrintWriter(result);
            e.printStackTrace(printWriter);
            return result.toString();
      }
     
      private String getStackCause(Throwable e) {
            StringBuffer sb = new StringBuffer();
            Throwable cause = e.getCause();
            while (cause != null) {
                  sb.append(readStackTrace(cause));
                  sb.append(separator);
                  cause = cause.getCause();
            }
            return sb.toString();
      }
     
      private void sendErrorMail(String ErrorContent) {
            Intent sendIntent = new Intent(Intent.ACTION_SEND);
            String subject = "Error Report";
            String body = ErrorContent;
//          sendIntent.putExtra(Intent.EXTRA_EMAIL,   new String[] {Constant.EMAIL_ID});
            sendIntent.putExtra(Intent.EXTRA_TEXT, body);
            sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
            sendIntent.setType("message/rfc822");
            mContext.startActivity(Intent.createChooser(sendIntent, "Title:"));
      }

      private void saveAsFile(String ErrorContent) {
            try {
                  long random = System.currentTimeMillis();
                  String FileName = "stack-" + random + ".stacktrace";
                  FileOutputStream trace = mContext.openFileOutput(FileName, Context.MODE_PRIVATE);
                  trace.write(ErrorContent.getBytes());
                  trace.close();
            } catch (IOException e) {}
      }

      public String[] getErrorFileList() {
            File dir = new File(filePath + "/");
            dir.mkdir();
            FilenameFilter filter = new FilenameFilter() {
                  public boolean accept(File dir, String name) {
                        return name.endsWith(".stacktrace");
                  }
            };
            return dir.list(filter);
      }

      public boolean isThereAnyErrorFile() {
            return getErrorFileList().length > 0;
      }

      public void checkErrorAndSendMail() {
            try {
                  if (isThereAnyErrorFile()) {
                        String wholeErrorText = "";
                        String[] ErrorFileList = getErrorFileList();
                        int curIndex = 0;
                        final int MaxSendMail = 5;
                        for (String curString : ErrorFileList) {
                              if (curIndex++ <= MaxSendMail) {
                                    wholeErrorText += "Trace collected :" + separator;
                                    wholeErrorText += "=====================" + separator;
                                    String fPath = filePath + "/" + curString;
                                    BufferedReader input = new BufferedReader( new FileReader(fPath));
                                    String line;
                                    while ((line = input.readLine()) != null) {
                                          wholeErrorText += line + "\n";
                                    }
                                    input.close();
                              }

                              File curFile = new File(filePath + "/" + curString);
                              curFile.delete();
                        }
                        sendErrorMail(wholeErrorText);
                  }
            } catch (Exception e) {
                  e.printStackTrace();
            }
      }
}

  • getInstance() - Make the single instance of CrashReporter class.
  • init() - Initialize context, Thread.setDefaultUncaughtExceptionHandler(this), filePath;
  • uncaughtException() - Crash is noticed and saved in a file.
  • readStackTrace() - Read the crashed stack trace.
  • getStackCause() - Read the Cause of crash.
  • sendErrorMail() - Prompt to send error mail.
  • saveAsFile() - Save the crash file so that next time on launch it will send.
  • getErrorFileList() - Get the list of crash files.
  • isThereAnyErrorFile() - Check if any crash file is present or not.
  • checkErrorAndSendMail() - Read the crash file, prompt to send then delete the same.
4) Now make a activity in which on clicking on button application crashed, and opening the application next time it will ask to send error mail.


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btnCrash"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Crash" />

</LinearLayout>

MainActivity.java

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.tutorialsface.errorhandler.crash.CrashReporter;

public class MainActivity extends Activity{
     
      Button btnCrash;
      CrashReporter reporter;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            btnCrash = (Button) findViewById(R.id.btnCrash);
            reporter = CrashReporter.getInstance();
            reporter.init(this);
            if (reporter.isThereAnyErrorFile()) {
                  showAlertDialog();
            }
            btnCrash.setOnClickListener(new OnClickListener() {
                  @Override
                  public void onClick(View v) {
                        int i = 1 / 0;
                  }
            });
      }
     
      private void showAlertDialog(){
            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
            alertDialogBuilder.setTitle("Send Error Log?");
            alertDialogBuilder.setMessage(
                              "A previous crash was reported. Would you like to send"
                             + " the developer the error log to fix this issue in the future?")
            .setCancelable(false)
            .setPositiveButton("OK",new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog,int id) {
                        reporter.checkErrorAndSendMail();
                  }
             })
            .setNegativeButton("No",new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog,int id) {
                        dialog.dismiss();
                  }
            });
            AlertDialog alertDialog = alertDialogBuilder.create();
            alertDialog.show();
      }
}

Initialize the CrashReporter by writing-
CrashReporter reporter = CrashReporter.getInstance();
reporter.init(this);

Then check if any crash file is present by - 
 reporter.isThereAnyErrorFile()

If yes then show the alert dialog for sending report.




1 comment: