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]; } } }