Recently, I sorted out the code of error log capture. Although there are many online, I added some ideas! The following code:
import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; public class CrashHandler implements UncaughtExceptionHandler { //Single case private volatile static CrashHandler instance; //Exception log path private File mLogPath; //Equipment information private String mDeviceInfo; //Default main thread exception handler private UncaughtExceptionHandler mDefaultHandler; //Initialize in application public static void init(Context context) { CrashHandler crashHandler = getInstance(); crashHandler.mLogPath = getCrashCacheDir(context); crashHandler.mDeviceInfo = CrashHandler.getDeviceInfo(context); crashHandler.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); //Set the main thread processor as a custom processor Thread.setDefaultUncaughtExceptionHandler(crashHandler); } //Get some device information private static String getDeviceInfo(Context context) { StringBuilder sb = new StringBuilder(); try { PackageManager pkgMgr = context.getPackageManager(); PackageInfo pkgInfo = pkgMgr.getPackageInfo(context.getPackageName(), 0); sb.append("packageName: ").append(pkgInfo.packageName).append("\n"); sb.append("versionCode: ").append(pkgInfo.versionCode).append("\n"); sb.append("versionName: ").append(pkgInfo.versionName).append("\n"); } catch (Exception e) { e.printStackTrace(); } //Manufacturers, such as Xiaomi, Meizu, Huawei, etc. sb.append("brand: ").append(Build.BRAND).append("\n"); //Full name of product model, e.g. meizu_mx3 sb.append("product: ").append(Build.PRODUCT).append("\n"); //Product model name, e.g. mx3 sb.append("device: ").append(Build.DEVICE).append("\n"); //Android version name, e.g. 4.4.4 sb.append("androidVersionName: ").append(Build.VERSION.RELEASE).append("\n"); //Android API version, e.g. 19 sb.append("androidApiVersion: ").append(Build.VERSION.SDK_INT).append("\n"); //Record all data of Build through reflection // Field[] fields = Build.class.getDeclaredFields(); // for (Field field : fields) { // try { // field.setAccessible(true); // infos.put(field.getName(), field.get(null).toString()); // Log.d(TAG, field.getName() + " : " + field.get(null)); // } catch (Exception e) { // Log.e(TAG, "an error occured when collect crash info", e); // } // } return sb.toString(); } //Get the log cache directory without permission private static File getCrashCacheDir(Context context) { String cachePath; if (context.getExternalCacheDir() != null) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getFilesDir().getPath(); } File dir = new File(cachePath + File.separator + "log"); if (!dir.exists()) { //noinspection ResultOfMethodCallIgnored dir.mkdirs(); } return dir; } //DCL double check public static CrashHandler getInstance() { if (instance == null) { synchronized (CrashHandler.class) { if (instance == null) { instance = new CrashHandler(); } } } return instance; } @Override public void uncaughtException(Thread thread, Throwable ex) { //Self handling exception handleException(ex); //Handle it by yourself and then hand it over to the default processor if (null != mDefaultHandler) { mDefaultHandler.uncaughtException(thread, ex); } } //Handling exceptions private void handleException(final Throwable throwable) { StringBuilder sb = new StringBuilder(); //Get exception information getExceptionInfo(sb, throwable); //Additional device information sb.append(mDeviceInfo); //Save to file new Thread(()->saveCrash2File(sb.toString())).start(); } //Get exception information private void getExceptionInfo(StringBuilder sb, Throwable throwable) { Writer writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); throwable.printStackTrace(printWriter); //Get the exception reason of the package exception in the loop Throwable cause = throwable.getCause(); while (cause != null) { cause.printStackTrace(printWriter); cause = cause.getCause(); } printWriter.close(); String exStr = writer.toString(); sb.append(exStr).append("\n"); } //Save to file private void saveCrash2File(String crashInfo) { try { // Used to format the date as part of the log file name DateFormat formatter = new SimpleDateFormat("yyMMddHHmmss", Locale.CHINA); String time = formatter.format(new Date()); String fileName = "crash_" + time + ".log"; String filePath = mLogPath.getAbsolutePath() + File.separator + fileName; FileOutputStream fos = new FileOutputStream(filePath); fos.write(crashInfo.getBytes()); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } //Get all log files public static List<File> getCrashLogs() { CrashHandler crashHandler = getInstance(); File crashDir = crashHandler.mLogPath; //Avoid using the asList method directly, and the generated view cannot be modified return new ArrayList<>(Arrays.asList(crashDir.listFiles())); } //Read the contents of the crash log file and return public static List<String> getCrashLogsStrings() { List<String> logs = new ArrayList<>(); List<File> files = getCrashLogs(); FileInputStream inputStream; for (File file : files) { try { inputStream = new FileInputStream(file); int len = 0; byte[] temp = new byte[1024]; StringBuilder sb = new StringBuilder(""); while ((len = inputStream.read(temp)) > 0){ sb.append(new String(temp, 0, len)); } inputStream.close(); logs.add(sb.toString()); } catch (Exception e) { e.printStackTrace(); } } return logs; } //Delete log file public static void removeCrashLogs(List<File> files) { for (File file : files) { //noinspection ResultOfMethodCallIgnored file.delete(); } } }
Let's briefly talk about some things in it.
Error log capture
In fact, the program crash is the crash of the main Thread. The program exit is actually that we did not catch the exception from the main Thread, and the Java Thread class contains a defaultUncaughtExceptionHandler variable, which will handle the exception of the Thread and print it to the default output, That is, when we debug, there is a problem. We can find the reason of the error log in the log.
// null unless explicitly set private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; ... public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { defaultUncaughtExceptionHandler = eh; }
Therefore, to capture the error log, we only need to implement the UncaughtExceptionHandler interface and set the processor of the main Thread to be customized. UncaughtExceptionHandler is also defined in the Thread class, as follows:
@FunctionalInterface public interface UncaughtExceptionHandler { /** * Method invoked when the given thread terminates due to the * given uncaught exception. * <p>Any exception thrown by this method will be ignored by the * Java Virtual Machine. * @param t the thread * @param e the exception */ void uncaughtException(Thread t, Throwable e); }
Get device information
Most of the time, the programs we have debugged run without problems, but once we arrive at the customer, it goes from one thing to another. This involves some equipment data, and our test is unlikely to cover all models. When there is a problem, we hope to submit the log and equipment information together.
Below are some device information. PackageManager can get the software version number and version name. Of course, filling in other flag s can get more other information. We won't elaborate here. The Build class contains some equipment information. I've selected some of them for use. You can also get all the data through reflection as in the comments.
//Get some device information private static String getDeviceInfo(Context context) { StringBuilder sb = new StringBuilder(); try { PackageManager pkgMgr = context.getPackageManager(); PackageInfo pkgInfo = pkgMgr.getPackageInfo(context.getPackageName(), 0); sb.append("packageName: ").append(pkgInfo.packageName).append("\n"); sb.append("versionCode: ").append(pkgInfo.versionCode).append("\n"); sb.append("versionName: ").append(pkgInfo.versionName).append("\n"); } catch (Exception e) { e.printStackTrace(); } //Manufacturers, such as Xiaomi, Meizu, Huawei, etc. sb.append("brand: ").append(Build.BRAND).append("\n"); //Full name of product model, e.g. meizu_mx3 sb.append("product: ").append(Build.PRODUCT).append("\n"); //Product model name, e.g. mx3 sb.append("device: ").append(Build.DEVICE).append("\n"); //Android version name, e.g. 4.4.4 sb.append("androidVersionName: ").append(Build.VERSION.RELEASE).append("\n"); //Android API version, e.g. 19 sb.append("androidApiVersion: ").append(Build.VERSION.SDK_INT).append("\n"); //Record all data of Build through reflection // Field[] fields = Build.class.getDeclaredFields(); // for (Field field : fields) { // try { // field.setAccessible(true); // infos.put(field.getName(), field.get(null).toString()); // Log.d(TAG, field.getName() + " : " + field.get(null)); // } catch (Exception e) { // Log.e(TAG, "an error occured when collect crash info", e); // } // } return sb.toString(); }
Get exception information
//Get exception information private void getExceptionInfo(StringBuilder sb, Throwable throwable) { Writer writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); throwable.printStackTrace(printWriter); //Get the exception reason of the package exception in the loop Throwable cause = throwable.getCause(); while (cause != null) { cause.printStackTrace(printWriter); cause = cause.getCause(); } printWriter.close(); String exStr = writer.toString(); sb.append(exStr).append("\n"); }
We need to use the Throwable class to get exceptions here, because the exceptions we mentioned may include Error and Exception, which need to be caught.
Since the exception may contain packaged case s, we need to cycle once to get the most original exception information.
For more information about exceptions, see my other blog:
https://blog.csdn.net/lfq88/article/details/107173268
Save log
//Get the log cache directory without permission private static File getCrashCacheDir(Context context) { String cachePath; if (context.getExternalCacheDir() != null) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getFilesDir().getPath(); } File dir = new File(cachePath + File.separator + "log"); if (!dir.exists()) { //noinspection ResultOfMethodCallIgnored dir.mkdirs(); } return dir; }
Log upload
I don't want to write about the log upload here, but I prefer to process it after getting the log data externally. The following provides a method to obtain abnormal data and a deletion method, which is used to clear the saved drop-down files after the log is uploaded successfully.
//Get all log files public static List<File> getCrashLogs() { CrashHandler crashHandler = getInstance(); File crashDir = crashHandler.mLogPath; //Avoid using the asList method directly, and the generated view cannot be modified return new ArrayList<>(Arrays.asList(crashDir.listFiles())); } //Read the contents of the crash log file and return public static List<String> getCrashLogsStrings() { List<String> logs = new ArrayList<>(); List<File> files = getCrashLogs(); FileInputStream inputStream; for (File file : files) { try { inputStream = new FileInputStream(file); int len = 0; byte[] temp = new byte[1024]; StringBuilder sb = new StringBuilder(""); while ((len = inputStream.read(temp)) > 0){ sb.append(new String(temp, 0, len)); } inputStream.close(); logs.add(sb.toString()); } catch (Exception e) { e.printStackTrace(); } } return logs; } //Delete log file public static void removeCrashLogs(List<File> files) { for (File file : files) { //noinspection ResultOfMethodCallIgnored file.delete(); } }
epilogue
I don't write much. I hope it will be helpful to readers. I also record it myself.
end