using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
///
/// 安卓平台文件日志记录器,用于保存闪退前的关键信息
///
public class AndroidFileLogger : MonoBehaviour
{
private static AndroidFileLogger _instance;
public static AndroidFileLogger Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType();
if (_instance == null)
{
var go = new GameObject("AndroidFileLogger");
_instance = go.AddComponent();
DontDestroyOnLoad(go);
}
}
return _instance;
}
}
private string _logDirectory;
private string _currentLogFile;
private Queue _logBuffer;
private readonly object _logLock = new object();
private Coroutine _logFlushCoroutine;
private bool _isLoggerInitialized;
private bool _isLogReceiverRegistered;
// 日志设置
private const int MAX_LOG_LINES = 2000; // 内存中最多保存2000行日志
private const int MAX_LOG_FILE_SIZE = 5 * 1024 * 1024; // 5MB最大文件大小
private const int MAX_LOG_FILES = 5; // 最多保留5个日志文件
void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
// 只在安卓平台启用
#if UNITY_ANDROID && !UNITY_EDITOR
InitializeLogger();
#endif
}
void Start()
{
#if UNITY_ANDROID && !UNITY_EDITOR
EnsureLoggerStarted();
LogInfo("AndroidFileLogger", "文件日志系统已启动");
LogInfo("AndroidFileLogger", $"日志目录: {_logDirectory}");
LogInfo("AndroidFileLogger", $"当前日志文件: {_currentLogFile}");
// 记录系统信息
LogSystemInfo();
#endif
}
private void InitializeLogger()
{
if (_isLoggerInitialized)
{
return;
}
try
{
// 获取安卓应用专用目录
_logDirectory = Path.Combine(Application.persistentDataPath, "logs");
// 确保目录存在
if (!Directory.Exists(_logDirectory))
{
Directory.CreateDirectory(_logDirectory);
}
// 创建新的日志文件
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
_currentLogFile = Path.Combine(_logDirectory, $"dcx_log_{timestamp}.txt");
// 初始化日志缓冲区
_logBuffer = new Queue();
// 清理旧日志文件
CleanupOldLogFiles();
_isLoggerInitialized = true;
EnsureLogReceiverRegistered();
Debug.Log($"[AndroidFileLogger] 初始化完成,日志路径: {_currentLogFile}");
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] 初始化失败: {ex.Message}");
}
}
private void EnsureLoggerStarted()
{
InitializeLogger();
EnsureLogReceiverRegistered();
if (_logFlushCoroutine == null)
{
_logFlushCoroutine = StartCoroutine(LogFlushCoroutine());
}
}
private void EnsureLogReceiverRegistered()
{
if (_isLogReceiverRegistered)
{
return;
}
Application.logMessageReceived += OnLogMessageReceived;
_isLogReceiverRegistered = true;
}
///
/// Unity日志消息回调
///
private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
if (string.IsNullOrEmpty(_logDirectory)) return;
// 过滤掉一些不重要的日志
if (ShouldSkipLog(condition, type)) return;
string logEntry = FormatLogEntry(condition, stackTrace, type);
lock (_logLock)
{
// 添加到缓冲区
_logBuffer.Enqueue(logEntry);
// 限制缓冲区大小
while (_logBuffer.Count > MAX_LOG_LINES)
{
_logBuffer.Dequeue();
}
// 如果是错误或异常,立即写入文件
if (type == LogType.Error || type == LogType.Exception || type == LogType.Assert)
{
FlushLogsToFile();
}
}
}
///
/// 判断是否跳过某些日志
///
private bool ShouldSkipLog(string condition, LogType type)
{
// 跳过过于频繁的普通日志
if (type == LogType.Log)
{
if (condition.Contains("[帧处理]") ||
condition.Contains("[队列]") && !condition.Contains("警告") && !condition.Contains("错误") ||
condition.Contains("OptimizedLog") ||
condition.Contains("Memory:") && !condition.Contains("警告"))
{
return true;
}
}
return false;
}
///
/// 格式化日志条目
///
private string FormatLogEntry(string condition, string stackTrace, LogType type)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
var typeStr = type.ToString();
var entry = new StringBuilder();
entry.AppendLine($"[{timestamp}] [{typeStr}] {condition}");
// 对于错误和异常,包含堆栈跟踪
if ((type == LogType.Error || type == LogType.Exception) && !string.IsNullOrEmpty(stackTrace))
{
entry.AppendLine($"StackTrace: {stackTrace}");
}
entry.AppendLine("---");
return entry.ToString();
}
///
/// 记录自定义日志
///
public void LogInfo(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
var logEntry = $"[{DateTime.Now:HH:mm:ss.fff}] [INFO] [{tag}] {message}\n---\n";
lock (_logLock)
{
_logBuffer.Enqueue(logEntry);
while (_logBuffer.Count > MAX_LOG_LINES)
{
_logBuffer.Dequeue();
}
}
#endif
}
///
/// 记录错误日志(立即写入文件)
///
public void LogError(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
var logEntry = $"[{DateTime.Now:HH:mm:ss.fff}] [ERROR] [{tag}] {message}\n---\n";
lock (_logLock)
{
_logBuffer.Enqueue(logEntry);
while (_logBuffer.Count > MAX_LOG_LINES)
{
_logBuffer.Dequeue();
}
// 立即刷写错误日志
FlushLogsToFile();
}
#endif
}
///
/// 记录内存使用情况
///
public void LogMemoryUsage(float memoryMB, string context = "")
{
#if UNITY_ANDROID && !UNITY_EDITOR
var message = string.IsNullOrEmpty(context)
? $"内存使用: {memoryMB:F1}MB"
: $"内存使用: {memoryMB:F1}MB - {context}";
LogInfo("Memory", message);
// 内存超过警告阈值时立即写入
if (memoryMB > 250f)
{
lock (_logLock)
{
FlushLogsToFile();
}
}
#endif
}
///
/// 记录系统信息
///
private void LogSystemInfo()
{
LogInfo("System", $"Unity版本: {Application.unityVersion}");
LogInfo("System", $"平台: {Application.platform}");
LogInfo("System", $"设备型号: {SystemInfo.deviceModel}");
LogInfo("System", $"设备名称: {SystemInfo.deviceName}");
LogInfo("System", $"操作系统: {SystemInfo.operatingSystem}");
LogInfo("System", $"系统内存: {SystemInfo.systemMemorySize}MB");
LogInfo("System", $"显卡内存: {SystemInfo.graphicsMemorySize}MB");
LogInfo("System", $"处理器: {SystemInfo.processorType}");
LogInfo("System", $"处理器核心数: {SystemInfo.processorCount}");
LogInfo("System", $"应用数据路径: {Application.persistentDataPath}");
}
///
/// 定期刷写日志协程
///
private IEnumerator LogFlushCoroutine()
{
while (true)
{
yield return new WaitForSeconds(10f); // 每10秒刷写一次
lock (_logLock)
{
if (_logBuffer.Count > 50) // 缓冲区有超过50条日志时才写入
{
FlushLogsToFile();
}
}
}
}
///
/// 刷写日志到文件
///
private void FlushLogsToFile()
{
try
{
if (_logBuffer.Count == 0 || string.IsNullOrEmpty(_currentLogFile)) return;
// 检查文件大小,如果过大则创建新文件
if (File.Exists(_currentLogFile))
{
var fileInfo = new FileInfo(_currentLogFile);
if (fileInfo.Length > MAX_LOG_FILE_SIZE)
{
CreateNewLogFile();
}
}
// 批量写入日志
var logsToWrite = new List();
while (_logBuffer.Count > 0 && logsToWrite.Count < 100) // 每次最多写100条
{
logsToWrite.Add(_logBuffer.Dequeue());
}
if (logsToWrite.Count > 0)
{
File.AppendAllText(_currentLogFile, string.Join("", logsToWrite), Encoding.UTF8);
}
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] 写入日志文件失败: {ex.Message}");
}
}
///
/// 创建新的日志文件
///
private void CreateNewLogFile()
{
try
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
_currentLogFile = Path.Combine(_logDirectory, $"dcx_log_{timestamp}.txt");
LogInfo("AndroidFileLogger", $"创建新日志文件: {_currentLogFile}");
CleanupOldLogFiles();
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] 创建新日志文件失败: {ex.Message}");
}
}
///
/// 清理旧日志文件
///
private void CleanupOldLogFiles()
{
try
{
if (!Directory.Exists(_logDirectory)) return;
var logFiles = Directory.GetFiles(_logDirectory, "dcx_log_*.txt");
// 按修改时间排序,保留最新的几个文件
Array.Sort(logFiles, (x, y) => File.GetLastWriteTime(y).CompareTo(File.GetLastWriteTime(x)));
// 删除多余的文件
for (int i = MAX_LOG_FILES; i < logFiles.Length; i++)
{
try
{
File.Delete(logFiles[i]);
Debug.Log($"[AndroidFileLogger] 删除旧日志文件: {Path.GetFileName(logFiles[i])}");
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] 删除日志文件失败: {ex.Message}");
}
}
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] 清理旧日志文件失败: {ex.Message}");
}
}
///
/// 应用暂停时保存日志
///
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
lock (_logLock)
{
FlushLogsToFile();
}
LogInfo("Application", "应用暂停,日志已保存");
}
}
///
/// 应用退出时保存日志
///
void OnApplicationQuit()
{
lock (_logLock)
{
FlushLogsToFile();
}
LogInfo("Application", "应用退出,日志已保存");
if (_logFlushCoroutine != null)
{
StopCoroutine(_logFlushCoroutine);
}
}
void OnDestroy()
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (_isLogReceiverRegistered)
{
Application.logMessageReceived -= OnLogMessageReceived;
_isLogReceiverRegistered = false;
}
// 最后一次保存日志
if (_logBuffer != null && _logBuffer.Count > 0)
{
try
{
FlushLogsToFile();
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] OnDestroy保存日志失败: {ex.Message}");
}
}
#endif
}
///
/// 获取当前日志文件路径
///
public string GetCurrentLogFilePath()
{
return _currentLogFile;
}
///
/// 获取所有日志文件路径
///
public string[] GetAllLogFiles()
{
try
{
if (!Directory.Exists(_logDirectory)) return new string[0];
var logFiles = Directory.GetFiles(_logDirectory, "dcx_log_*.txt");
Array.Sort(logFiles, (x, y) => File.GetLastWriteTime(y).CompareTo(File.GetLastWriteTime(x)));
return logFiles;
}
catch (Exception ex)
{
Debug.LogError($"[AndroidFileLogger] 获取日志文件列表失败: {ex.Message}");
return new string[0];
}
}
}