android: 记录app运行过程中产生的log

有时在解决问题时,经常需要借助logcat才能分析定位问题,这里写了一个小工具,能够记录app运行期间的log, 这样测试人员在反馈bug时,只需要把logcat发给我们就可以了。具体代码如下:

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * A tool class is used to capture the logcat generated by the app running.
 * <p>The logcat save path: sdcard/Android/data/packageName/files/Documents/logcatSaveDir</p>
 *
 * @author xp.chen
 */
public class LogWatcher {

    public static final String TAG = LogWatcher.class.getSimpleName();

    private static volatile LogWatcher instance = null;

    private static final String LOG_FILE_PREFIX = "logcat_";
    private static final String LOG_FILE_SUFFIX = ".txt";

    private static String sLogDirPath;

    private Context mContext;

    private Process mLogcatProcess;

    private File mLogcatFile;

    private LogWatcher() {}

    public static LogWatcher getInstance() {
        if (instance == null) {
            synchronized (LogWatcher.class) {
                if (instance == null) {
                    instance = new LogWatcher();
                }
            }
        }
        return instance;
    }

    /**
     * Init the logcat watcher.
     *
     * @param context    Application context.
     * @param logDirName Logcat save dir
     * @return LogcatWatcher instance.
     */
    public LogWatcher init(Context context, String logDirName) {
        if (context == null)
            throw new IllegalArgumentException("LogWatcher: init failed, context can not be null");

        if (TextUtils.isEmpty(logDirName))
            throw new IllegalArgumentException("LogWatcher: init failed, logDirName can not be null");

        this.mContext = context.getApplicationContext();
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File documentFileDir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
            if (documentFileDir != null) {
                sLogDirPath = documentFileDir.getAbsolutePath() + File.separator + logDirName;
            } else {
                Log.e(TAG, "LogWatcher: init LogWatcher failed!");
            }
        } else {
            sLogDirPath = mContext.getFilesDir().getAbsolutePath() + File.separator + logDirName;
        }

        return this;
    }

    /**
     * Start capture the logcat generated by the app.
     */
    public void startWatch() {
        stopWatch();

        if (TextUtils.isEmpty(sLogDirPath)) {
            Log.e(TAG, "LogWatcher: can not watch log, the log dir can not be created");
            return;
        }

        mLogcatFile = createNewLogFile();
        if (mLogcatFile == null) {
            Log.e(TAG, "LogWatcher: can not create new log file");
            return;
        } else {
            Log.i(TAG, "LogWatcher: log file save path >>> " + mLogcatFile.getAbsolutePath());
        }

        // Clear cache log
        try {
            Process process = Runtime.getRuntime().exec("logcat -c");
            process.destroy();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //final String LOGCAT_FILTER_CMD = "logcat -v time *:v | grep \"(" + android.os.Process.myPid() + ")\" > " + newLogFile.getAbsolutePath();
        final String LOGCAT_FILTER_CMD = "logcat -v time *:V -f " + mLogcatFile.getAbsolutePath();

        try {
            mLogcatProcess = Runtime.getRuntime().exec(LOGCAT_FILTER_CMD);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Stop capture the logcat generated by the app.
     */
    public void stopWatch() {
        if (mLogcatProcess != null) {
            mLogcatProcess.destroy();
            mLogcatProcess = null;
        }

        if (mLogcatFile != null) {
            notifySystemToScan(mContext, mLogcatFile);
        }
    }

    private File createNewLogFile() {
        File logSaveDir = new File(sLogDirPath, getCurrentDateStr());
        if (!logSaveDir.exists()) {
            boolean mkdirs = logSaveDir.mkdirs();
            if (!mkdirs) {
                Log.e(TAG, "LogWatcher: create new save dir failed");
                return null;
            }
        }

        String logFileName = LOG_FILE_PREFIX + getCurrentTimeStr() + LOG_FILE_SUFFIX;
        File logFile = new File(logSaveDir, logFileName);

        try {
            boolean createRet = logFile.createNewFile();
            if (!createRet) {
                Log.e(TAG, "LogWatcher: create new log file failed");
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return logFile;
    }

    private static String getCurrentTimeStr() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault());
        return sdf.format(new Date(System.currentTimeMillis()));
    }

    private static String getCurrentDateStr() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
        return sdf.format(new Date(System.currentTimeMillis()));
    }

    private static void notifySystemToScan(Context context, File file) {
        if (context == null || file == null) return;
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(file);
        intent.setData(uri);
        context.sendBroadcast(intent);
    }

}

为了简化使用,我把所有相关的操作都放在一个类里去完成了,这样只需要包含这一个文件即可。notifySystemToScan()的作用是为了在写入完成后能够刷新logcat文件,否则写入完毕后连上电脑在Windows上看不到文件。

默认保存路径为:Android/data/packageName/files/Documents/Logcat目录下。为了方便查看,在保存时我按照日期创建了不同的文件夹对logcat进行保存。

主要实现思想利用了adb logcat -f 这个指令,它可以持续不断的向某个文件中写入数据。

开始记录:

private void startLogWatcher() {
        LogWatcher.getInstance().init(getApplicationContext(), PathDefine.LOG_SAVE_DIR).startWatch();
    }

停止记录:

LogWatcher.getInstance().stopWatch();

运行截图:

(完)



如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   夜行过客  阅读(2064)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示