UniApp 安卓端实现文件的生成,写入,获取文件大小以及压缩功能

生产环境,app应用不能像PC端一样,可以在浏览器控制台调试,所以应用加了日志打印的开关,需要实现日志文件的生成,写入,获取大小以及压缩功能,方便后期定位数据问题。

一、创建公共文件

引入与定义变量
javascript 复制代码
// utils/logService.js
import moment from 'moment';

let logFileEntry = null; // 判断是否存在上次生成文件的依据
const LOG_ENTRY_KEY = 'log_file_entry'; // 存到本地存储的key值
const time = moment().format("YYYY-MM-DD HH:mm:ss"); // 生成文件名所用

export const logService = {
	// 所有方法都是放在这里的
	// 初始化
	async init() {
		// 如果已有引用,直接返回
		if (logFileEntry) {
			return logFileEntry;
		}
		// 先从本地存储中获取
		const cachedEntry = await this.getStoredLogFileEntry();
		// 如果缓存中存在,则使用缓存的引用
		if (cachedEntry && cachedEntry.fullPath) {
			try {
				// 尝试恢复文件引用
				logFileEntry = await this.restoreFileEntry(cachedEntry);
				return logFileEntry;
			} catch (error) {
				console.warn('恢复缓存文件引用失败,将创建新文件:', error);
			}
		}
		// 缓存不存在或恢复失败,创建新文件
		if (!logFileEntry) {
			try {
				const res = await this.createLogFile();
				logFileEntry = res.fileEntry;
			} catch (error) {
				console.error('初始化日志文件失败:', error);
				throw error;
			}
		}
		return logFileEntry;
	}, 
	...
}

二、生成txt文件

javascript 复制代码
// 创建当前时间日志文件
createLogFile() {
	const fileName = `appLog_${time}.txt`; 
	return new Promise((resolve, reject) => {
		plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
			fs.root.getFile(fileName, {
				create: true
			}, (fileEntry) => {
				// 获取文件路径
				fileEntry.file((file) => {
					const filePath = fileEntry.toLocalURL();
					// 清空缓存
					this.clearStoredLogFileEntry();
					
					const initData = `创建应用日志文件`;
					// 初始数据写入
					this.writeLogContent(fileEntry, initData)
						.then(() => {
							// 创建成功后立即存储到缓存
							this.storeLogFileEntry(fileEntry);

							resolve({
								fileEntry,
								filePath
							});
						})
						.catch((writeError) => {
							// 新文件信息已存储到缓存(写入失败)
							this.storeLogFileEntry(fileEntry);
							resolve({
								fileEntry,
								filePath
							});
						});
				}, (fileError) => {
					// 即使获取文件信息失败,也返回文件对象
					resolve(fileEntry);
				});
			}, (getFileError) => {
				reject(getFileError);
			});
		}, (fsError) => {
			reject(fsError);
		});
	});
},

三、写入内容

javascript 复制代码
// 写入日志(提供该方法给其他页面组件调用,写入之前初始化判断文件引用是否存在)
async writeLog(content) {
	try {
		await this.init();
		return this.writeLogContent(logFileEntry, content);
	} catch (error) {
		console.error('写入日志失败:', error);
	}
}, 
/**
 * 写入文件
 * @param {String} 文件路径
 * @returns 当前日志文件引用
 * @returns {String} 写入内容
 */
writeLogContent(logFileEntry, content) {
	return new Promise((resolve, reject) => {
		const time = moment().format("YYYY-MM-DD HH:mm:ss");

		logFileEntry.createWriter((writer) => {
			writer.seek(writer.length);

			// 格式化内容
			let formattedContent = content;
			if (typeof content === 'object' && content !== null) {
				formattedContent = JSON.stringify(content, null, 2);
			}

			// 添加时间和换行
			const logEntry = `\n[${time}] \n ${formattedContent}`;
			// const blob = new Blob([logEntry], { type: 'text/plain;charset=utf-8' });
			writer.write(logEntry);

			writer.onwrite = () => {
				resolve();
			};

			writer.onerror = (error) => {
				console.error('日志写入失败:', error);
				reject(error);
			};
		}, reject);
	});
},
页面组件调用写入方法
javascript 复制代码
import {
	logService
} from '@/utils/logService';

// 写入文件
writeToFile() {
	const data = "写入内容"
	logService.writeLog(data);
}

四、获取文件大小

javascript 复制代码
// 获取日志文件信息(路径和大小)
async getLogFileInfo() {
	try {
		const filePath = await this.getCurrentLogFilePath();
		if (!filePath) {
			return {
				filePath: null,
				fileSize: '0KB'
			};
		}

		const fileSize = await this.getFileSize(filePath);
		return {
			filePath,
			fileSize
		};
	} catch (error) {
		console.error('获取日志文件信息失败:', error);
		return {
			filePath: null,
			fileSize: '0KB'
		};
	}
},
页面组件调用获取大小方法
javascript 复制代码
import {
	logService
} from '@/utils/logService';
 
// 更新日志文件信息
async updateLogFileInfo() {
	try {
		const fileInfo = await logService.getLogFileInfo(); // fileInfo:filePath、fileSize
		// 执行其他操作
},

五、文件压缩成zip

javascript 复制代码
/**
* 压缩日志文件-zip
* @returns {Promise} 返回一个Promise,包含上传结果
*/
async compressLog() {
	// 确保日志文件已初始化
	await this.init();
	if (!logFileEntry) {
		throw new Error('日志文件未初始化');
	} 
	
	// 验证日志文件是否可用
	if (!logFileEntry.toLocalURL) {
		console.error('日志文件引用无效');
		return;
	}
	
	const logFilePathOrigin = logFileEntry.toLocalURL(); 
	let logFilePath = logFilePathOrigin;
	// 移除可能的file://前缀
	if (logFilePath.startsWith('file://')) {
		logFilePath = logFilePath.substring(7);
	}
	
	// 检查文件是否存在
	const isExist = await this.fileExists(logFilePathOrigin);
	if (!isExist) {
		console.error("日志文件不存在:", logFilePathOrigin);
		return;
	}
	
	// 获取临时目录
	const tempFs = await new Promise((resolve, reject) => {
		plus.io.requestFileSystem(plus.io.PRIVATE_DOC, resolve, reject);
	});
	const tempDir = tempFs.root.fullPath;
	if (!tempDir.endsWith('/')) {
		tempDir += '/';
	}
	
	// 构建ZIP文件名
	const logFileName = logFileEntry.name;
	const logTimestampMatch = logFileName.match(/_([^\.]+)\./); // 正则取出_和.之间的,也就是时间:2025-12-17 15:38:43
	const logTimestamp = logTimestampMatch?.length && logTimestampMatch[1] || time; // 取txt文件的时间作为zip的时间
	const zipFileName = `appLog_${logTimestamp}.zip`;
	const zipFilePath = `${tempDir}${zipFileName}`;
	const zipFilePathOrigin = `file://${zipFilePath}`; 
	
	// 检查并删除已存在的zip文件
	try {
		// 判断压缩文件是否存在
		const zipExists = await this.fileExists(zipFilePathOrigin);
		if (zipExists) { 
			// 删除压缩文件
			await this.deleteFile(zipFilePathOrigin);
		}
	} catch (e) {
		console.warn("清理旧zip文件失败:", e);
	}
	
	// 检查plus.zip对象是否存在
	if (!plus.zip || !plus.zip.compress) { 
		return;
	} 
	
	// 返回Promise
	return new Promise((resolve, reject) => {
		plus.zip.compress(
			logFilePath,
			zipFilePath,
			async (result) => {
					try {
						// 判断是否压缩成功
						const exists = await this.fileExists(zipFilePathOrigin);
						if (exists) {
							resolve({
								success: true,
								zipFilePath: zipFilePathOrigin,
								zipFileName: zipFileName,
								message: '压缩成功'
							});
						} else {
							reject(new Error('压缩文件未创建'));
						}
					} catch (error) {
						reject(error);
					}
				},
				(error) => {
					const errorMsg = error && (error.message || JSON.stringify(
						error)); 
					reject(new Error(`压缩失败: ${errorMsg}`));
				}
		);
	})
},
页面组件调用压缩方法
javascript 复制代码
import {
	logService
} from '@/utils/logService';

// 压缩日志文件
async handleCompress() {
	const zipRes = await logService.compressLog();
	if (zipRes?.success) {
		// 压缩成功,执行其他操作,比如上传
	}
}

六、工具类方法

javascript 复制代码
// 检查文件是否存在
async fileExists(filePath) {
	if (!filePath) {
		return false;
	}
	// 统一转为file://格式
	let checkPath = filePath;
	if (!checkPath.startsWith('file://')) {
		checkPath = `file://${checkPath}`;
	}
	return new Promise((resolve) => {
		// 使用resolveLocalFileSystemURL检查文件
		plus.io.resolveLocalFileSystemURL(
			checkPath,
			(entry) => {
				resolve(true);
			},
			(err) => { 
				resolve(false);
			}
		);
	});
},
// 删除文件
deleteFile(filePath) {
	return new Promise((resolve, reject) => {
		plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
			entry.remove(() => {
				resolve(true);
			}, (error) => {
				reject(error);
			});
		}, (error) => {
			// 文件不存在也算成功
			resolve(false);
		});
	});
},

// 获取当前日志文件路径
async getCurrentLogFilePath() {
	try {
		// 确保已初始化
		await this.init();
		if (logFileEntry) {
			return logFileEntry.toLocalURL();
		}
		return null;
	} catch (error) {
		console.error('获取日志文件路径失败:', error);
		return null;
	}
},
// 从本地存储获取日志文件引用
getStoredLogFileEntry() {
	try {
		const entryInfo = uni.getStorageSync(LOG_ENTRY_KEY);
		if (entryInfo && entryInfo.fullPath) {
			return entryInfo;
		}
	} catch (error) {
		console.error('读取缓存失败:', error);
	}
	return null;
},

// 存储日志文件引用到本地存储
storeLogFileEntry(fileEntry) {
	try {
		const cacheInfo = {
			fullPath: fileEntry.fullPath,
			name: fileEntry.name,
			toLocalURL: fileEntry.toLocalURL(),
			toURL: fileEntry.toURL(),
			timestamp: Date.now()
		}; 
		uni.setStorageSync(LOG_ENTRY_KEY, cacheInfo);
	} catch (error) {
		console.error('缓存日志文件信息失败:', error);
	}
},

// 清空存储的日志文件引用
clearStoredLogFileEntry() {
	try {
		uni.removeStorageSync(LOG_ENTRY_KEY);
		logFileEntry = null;
	} catch (error) {
		console.error('清空日志文件缓存失败:', error);
	}
},
// 从缓存信息恢复文件引用
async restoreFileEntry(cacheInfo) {
	return new Promise((resolve, reject) => {
		if (!cacheInfo || !cacheInfo.fullPath) {
			reject(new Error('缓存信息无效'));
			return;
		} 
		// 使用 fullPath 或 toLocalURL 恢复文件引用
		const filePath = cacheInfo.fullPath || cacheInfo.toLocalURL;

		if (!filePath) {
			reject(new Error('缓存中没有有效的文件路径'));
			return;
		}

		plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
			// 验证文件是否存在并可写
			entry.createWriter((writer) => {
				// 如果能创建writer,说明文件可访问
				resolve(entry);
			}, (writerError) => {
				// 即使创建writer失败,也返回entry
				resolve(entry);
			});
		}, (error) => {
			reject(null);
		});
	});
},
相关推荐
2501_915921432 小时前
uni-app 的 iOS 打包与上架流程,多工具协作
android·ios·小程序·uni-app·cocoa·iphone·webview
Lei活在当下9 小时前
【Perfetto从入门到精通】4.使用 heapprofd 工具采样追踪 Java/Native 内存分配
android·性能优化·架构
alexhilton10 小时前
学会在Jetpack Compose中加载Lottie动画资源
android·kotlin·android jetpack
信看10 小时前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
爱吃大芒果10 小时前
Flutter 主题与深色模式:全局样式统一与动态切换
开发语言·javascript·flutter·ecmascript·gitcode
king王一帅11 小时前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
summerkissyou198712 小时前
Android-Camera-为啥不移到packages/module
android·相机
liang_jy12 小时前
Android UID
android·面试