EN

Android Tools - Log2File

字数统计: 1.2k阅读时长: 4 min
2014/05/02
loading

2026-10-13 更新:2014 年版本直接向外部存储根目录写文件,这种用法已过时。Android 10(API 29)引入分区存储,Android 11(API 30)对 targetSdk 30 及以上应用忽略 requestLegacyExternalStorage。下面改用应用内部目录,示例最低版本为 Android 4.4(API 19),不需要存储权限。Android 11 存储变化

Log2File 工具类用于 Android 程序把应用主动记录的日志写入文件,适合下面这些场景:

  1. 无法连接电脑进行调试,例如 USB 口被 OTG 设备占用。
  2. 日志不容易实时抓取。
  3. Bug 出现很随机,需要保留操作过程中的记录。
  4. 其他自己脑补。

它只保存传给 w() 的内容,不会自动读取系统 logcat;系统日志抓取仍使用 Logcat 工具。

0.说明

这篇文章最早发布在我的 CSDN 博客,后来迁移到这里。原 GitHub 源码地址在本次更新时返回 404,因此下面直接保留可阅读的替代实现。

1.Log2File 工具类源码

原实现使用静态 writer 和独立的 logInit 标志,并发写入、关闭时容易发生状态竞争。下面把一次记录过程交给一个对象管理,用同一把锁保护写入和关闭;初始化、写入或关闭失败会抛出 IOException,由调用方处理。

日志放在 context.getFilesDir()/log2file/。这个目录属于应用内部存储,不需要申请权限,卸载应用时会删除;如需分享日志,应通过 FileProvider 等方式临时授权读取。应用专属文件

代码使用 UTF-8 和 Unix epoch 毫秒时间戳,避免原 Date.toLocaleString() 带来的过时 API 与本地化格式差异。StandardCharsets 从 API 19 可用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import android.content.Context;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

public final class Log2File implements Closeable {
private final BufferedWriter writer;

public Log2File(Context context, String fileName) throws IOException {
if (fileName == null || fileName.isEmpty() || fileName.contains("/")
|| fileName.equals(".") || fileName.equals("..")) {
throw new IllegalArgumentException("Use a simple file name, not a path");
}
File directory = new File(context.getFilesDir(), "log2file");
if (!directory.isDirectory() && !directory.mkdirs() && !directory.isDirectory()) {
throw new IOException("Cannot create log directory");
}
File file = new File(directory, fileName);
writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file, true), StandardCharsets.UTF_8));
}

public synchronized void w(String message) throws IOException {
writer.write("[" + System.currentTimeMillis() + "] " + message);
writer.newLine();
writer.flush();
}

@Override
public synchronized void close() throws IOException {
writer.close();
}
}

每个文件在一个进程内应由同一个 Log2File 对象管理;synchronized 不会协调两个不同对象或不同进程。调用 close() 后继续写入会抛出异常;不要让记录任务和资源所有者各自随意关闭同一个对象。

这里每条日志都 flush(),适合低频、短时间诊断,但会增加 I/O。flush() 只把 Java 缓冲交给下层流,不保证断电后仍能恢复。长时间或高频采集需要明确文件大小、保留期限和写入队列上限,按场景采用批量写入及日志轮转;这个最小示例没有实现这些策略。BufferedWriter

2.Log2File 类的使用

新版使用构造函数打开文件,替代旧的静态 init()。在后台任务中创建实例,写入需要保留的事件,任务结束时关闭:

1
2
3
4
5
6
7
8
// Run this on a background worker, not the UI thread.
try (Log2File log = new Log2File(context, "diagnostic.log")) {
log.w("Capture started");
// Perform the short diagnostic operation and record its relevant events.
log.w("Capture finished");
} catch (IOException error) {
android.util.Log.e("DiagnosticCapture", "Could not write diagnostic log", error);
}

try-with-resources 会在正常结束和异常退出时调用 close()。需要跨多个回调记录时,由同一个任务或组件持有实例,等所有写入完成后统一关闭,不要为每条日志重新打开文件。

日志只记录排查所需的信息,避免把密码、令牌或完整用户输入写进去。应用内部目录解决的是文件归属和访问权限,不能替代内容脱敏和保留期限管理。

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 0.说明
  2. 2. 1.Log2File 工具类源码
  3. 3. 2.Log2File 类的使用
  • 关于我 && 博客