Android-实现记录“异常闪退“日志

1,引文

在开发进程中,因某些未知缘由,应用在运行时异常闪退,然而却无法看到错误日志,着实令人难受。

2,解决思路

借助 Process 来启动一个进程去执行 Logcat 命令,并把内容存储至指定文件内。如此一来,即便应用闪退,依然能够记录下关键信息(进程之间彼此独立)。

3,关键步骤

3.1,启动Logcat进程
Process process = new ProcessBuilder("logcat").redirectErrorStream(true).start();
3.2,读取并保存Process输出内容
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));) {
	FileWritter writter = ...;
	String content;
	while (process.isAlive() && (content = reader.readLine()) != null) {
		writer.write(content);
        writer.write("\n");
        writer.flush();
	}
} ...
3.3,关闭Logcat进程
if (process != null && process.isAlive()) {
	process.destroyForcibly();
}

4,简单封装LoggerUtil工具

import android.app.Activity;
import android.util.Log;

import androidx.annotation.NonNull;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;

public class LoggerUtil {

    @SuppressWarnings("unused")
    private static final String TAG = "LoggerUtil";

    /**
     * logcat进程对象
     */
    private static final AtomicReference<Process> gProcess = new AtomicReference<>(null);
    /**
     * logcat进程对象的创建器
     */
    private static final ProcessBuilder logcatBuilder = new ProcessBuilder("logcat");

    /**
     * 日志内容过滤器
     */
    private static Set<LogContentFilter> logContentFilters;
    /**
     * 日志目录名
     */
    private static String logDirName;
    /**
     *  日志文件名
     */
    private static String logFileName;
    /**
     * 单个日志文件最大大小
     */
    private static long logFileMaxSize;
    /**
     * 日志文件备份名获取类
     */
    private static BackupLogFileNameGetter backupLogFileNameGetter;

    /**
     * 日志内容过滤器基类
     */
    public interface LogContentFilter {
        /**
         * @param content 日志内容
         * @return true表示记录该内容,false不记录该内容
         */
        boolean isRecord(@NonNull String content);
    }

    /**
     * 日志等级
     */
    public enum Level {
        INFO("I"),
        WARN("W"),
        ERROR("E"),
        DEBUG("D"),
        VERBOSE("V"),
        ALL(".")
        ;
        final String value;
        Level(String value) {
            this.value = value;
        }
        public String getValue() {
            return value;
        }
    }

    /**
     * 默认通过日志等级进行过滤
     */
    public static class DefaultLogContentFilter implements LogContentFilter {
        private final Pattern p;

        public DefaultLogContentFilter(Level level) {
            p = Pattern.compile("^[0-9 -.:]* " + level.getValue() + " .*$");
        }

        @Override
        public boolean isRecord(@NonNull String content) {
            return p.matcher(content).matches();
        }
    }

    /**
     * 日志文件备份名获取基类
     */
    public interface BackupLogFileNameGetter {
        String get(String logFileName);
    }

    /**
     * 默认在当前文件名后面添加当前时间作为备份名
     */
    public static class DefaultBackupLogFileNameGetter implements BackupLogFileNameGetter {
        private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.ROOT);
        @Override
        public String get(String logFileName) {
            String bakLogFileFormat = "%s_%s.log";
            return String.format(bakLogFileFormat, logFileName, fileDateFormat.format(new Date()));
        }
    }

    /**
     * 参数构造器
     */
    public static class Builder {
        /**
         * 日志内容过滤器
         */
        private final Set<LogContentFilter> logContentFilters = new HashSet<>();
        /**
         * 日志文件夹名字
         */
        private String logDirName = "logs";
        /**
         * 日志文件名
         */
        private String logFileName = "app.log";
        /**
         * 单个日志文件最大大小
         */
        private long logFileMaxSize = 5 * 1024 * 1024; //MB
        /**
         * 日志文件备份名获取类
         */
        private BackupLogFileNameGetter backupLogFileNameGetter = new DefaultBackupLogFileNameGetter();

        public Builder() {
            logContentFilters.add(new DefaultLogContentFilter(Level.WARN));
            logContentFilters.add(new DefaultLogContentFilter(Level.ERROR));
            logContentFilters.add(new DefaultLogContentFilter(Level.DEBUG));
            logContentFilters.add(new DefaultLogContentFilter(Level.VERBOSE));
        }

        public Builder setBackupLogFileNameGetter(BackupLogFileNameGetter backupLogFileNameGetter) {
            if (backupLogFileNameGetter == null) return this;
            this.backupLogFileNameGetter = backupLogFileNameGetter;
            return this;
        }

        public Builder setLogFileName(String logFileName) {
            if (logFileName == null || logFileName.trim().isEmpty()) return this;
            this.logFileName = logFileName;
            return this;
        }

        public Builder setLogDirName(String logDirName) {
            if (logDirName == null || logDirName.trim().isEmpty()) return this;
            this.logDirName = logDirName;
            return this;
        }

        /**
         * 单位"B",最小为512KB <br/>
         * 1MB = 1024KB <br/>
         * 1KB = 1024B
         */
        public Builder setLogFileMaxSize(long logFileMaxSize) {
            if (logFileMaxSize < 512 * 1024) return this;
            this.logFileMaxSize = logFileMaxSize;
            return this;
        }

        public Builder addLogContentFilter(LogContentFilter logContentFilter) {
            if (logContentFilter == null) return this;
            logContentFilters.add(logContentFilter);
            return this;
        }

        public Builder addLogContentFilters(List<LogContentFilter> logContentFilters) {
            if (logContentFilters == null || logContentFilters.isEmpty()) return this;
            this.logContentFilters.addAll(logContentFilters);
            return this;
        }

        public Builder setLogContentFilter(LogContentFilter logContentFilter) {
            logContentFilters.clear();
            return addLogContentFilter(logContentFilter);
        }

        public Builder setLogContentFilters(List<LogContentFilter> logContentFilters) {
            this.logContentFilters.clear();
            return addLogContentFilters(logContentFilters);
        }
    }

    public static void start(Activity activity) {
        start(activity, null);
    }

    public static void start(Activity activity, Builder builder) {
        bind(builder);
        synchronized (gProcess) {
            stop();
            try {
                gProcess.set(logcatBuilder.redirectErrorStream(true).start());
            } catch (Exception e) {
                Log.e(TAG, "开启Logcat失败", e);
                return;
            }
        }

        final File logRootDir = activity.getExternalFilesDir(null);
        if (logRootDir == null) {
            stop();
            Log.e(TAG, "getExternalFilesDir获取失败");
            return;
        }
        CompletableFuture.runAsync(() -> {
            File logFile = getLogFile(logRootDir);
            if (logFile == null) {
                stop();
                return;
            }
            Process process = gProcess.get();
            FileWriter writer = null;
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));) {
                writer = new FileWriter(logFile, true);
                String content;
                while (process.isAlive() && (content = reader.readLine()) != null) {
                    if (content.trim().length() == 0 || logContentFilters.isEmpty()) continue;
                    boolean needRecord = true;
                    for (LogContentFilter logFilter : logContentFilters) {
                        if (!logFilter.isRecord(content)) {
                            needRecord = false;
                            break;
                        }
                    }
                    if (!needRecord) continue;

                    writer.write(content);
                    writer.write("\n");
                    writer.flush();

                    if (logFile.length() >= logFileMaxSize) {
                        logFile = getLogFile(logRootDir);
                        if (logFile == null) {
                            stop();
                            return;
                        }
                        writer.close();
                        writer = new FileWriter(getLogFile(logRootDir), true);
                    }
                }
            } catch (Exception ignored) {
            } finally {
                stop();
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException ignored) {
                    }
                }
            }
        }, Executors.newSingleThreadExecutor());
    }

    /**
     * 绑定Builder数据
     */
    private static void bind(Builder builder) {
        if (builder == null) {
            builder = new Builder();
        }
        logContentFilters = builder.logContentFilters;
        logDirName = builder.logDirName;
        logFileName = builder.logFileName;
        logFileMaxSize = builder.logFileMaxSize;
        backupLogFileNameGetter = builder.backupLogFileNameGetter;
    }

    private static File getLogFile(@NonNull File logRootDir) {
        File logDir = new File(logRootDir, logDirName);
        if (!logDir.exists() && !logDir.mkdirs()) {
            Log.e(TAG, "创建日志文件目录失败");
            return null;
        }
        File logFile = new File(logDir, logFileName);
        try {
            if (!logFile.exists() && !logFile.createNewFile()) {
                Log.e(TAG, "创建日志文件失败");
                return null;
            }
        } catch (IOException e) {
            Log.e(TAG, "创建日志文件失败", e);
            return null;
        }
        long fileSize = logFile.length();
        if (fileSize >= logFileMaxSize) {
            File bakFile = new File(logDir, backupLogFileNameGetter.get(logFileName));
            if (logFile.renameTo(bakFile)) {
                Log.e(TAG, "重命名日志文件失败");
                return null;
            }
            logFile = new File(logDir, logFileName);
        }

        return logFile;
    }

    public static void stop() {
        synchronized (gProcess) {
            Process process = gProcess.get();
            if (process != null && process.isAlive()) {
                process.destroyForcibly();
            }
            gProcess.set(null);
        }
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Android应用中,可以通过全局捕获异常来避免应用退。可以通过以下步骤实现: 1. 创建一个自定义的Application类,并在其中重写`Thread.UncaughtExceptionHandler`接口的`uncaughtException`方法。 2. 在`uncaughtException`方法中处理全局异常,例如记录异常信息、上传日志或者进行其他处理操作。 3. 在Application的onCreate方法中,将自定义的UncaughtExceptionHandler设置为默认的异常处理器。 下面是一个示例代码: ```java public class MyApplication extends Application implements Thread.UncaughtExceptionHandler { @Override public void onCreate() { super.onCreate(); // 设置全局异常处理器 Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread thread, Throwable ex) { // 处理全局异常,例如记录异常信息、上传日志等操作 Log.e("MyApplication", "Uncaught Exception: " + ex.getMessage()); // 重启应用或者执行其他操作 restartApp(); } private void restartApp() { // 重启应用,可以根据实际需求来实现 Intent intent = new Intent(getApplicationContext(), MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT); AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent); // 退出应用 System.exit(0); } } ``` 记得在AndroidManifest.xml文件中将自定义的Application类配置为应用的默认Application类: ```xml <application android:name=".MyApplication" ...> ... </application> ``` 通过以上步骤,当应用发生未捕获的异常时,会调用自定义的异常处理方法,你可以在其中进行相应的处理操作,例如记录异常信息、上传日志等。最后,你可以选择重启应用或者执行其他操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值