Uniapp 5+API 文件读写:从基础到实战的深度解析与封装实践

在移动应用开发中,本地文件操作是一个绕不开的话题。无论是离线数据缓存、用户配置存储、日志记录,还是多媒体文件管理,都需要与设备的文件系统打交道。对于使用 Uniapp 进行跨平台开发的开发者来说,5+API 中的 IO 模块提供了强大而灵活的本地文件操作能力。但很多开发者在使用过程中,常常会遇到各种问题:权限问题、路径问题、异步回调处理、错误处理不完善等。

这篇文章将从实战角度出发,深入解析 Uniapp 中 5+API 文件读写的完整流程。我不会简单地重复官方文档的内容,而是结合我多年的开发经验,分享在实际项目中遇到的坑和解决方案,并提供一套经过实战检验的封装方案。无论你是刚刚接触 Uniapp 文件操作的新手,还是希望优化现有代码的老手,这篇文章都能给你带来新的启发。

1. 理解 5+API IO 模块的核心概念与架构

在开始编写代码之前,我们需要先理解 5+API IO 模块的基本架构。这个模块的设计遵循了 W3C 文件系统 API 的标准,但针对移动端做了优化和扩展。理解这个架构,能帮助我们在遇到问题时更快地定位原因。

1.1 文件系统类型与访问权限

5+API 提供了多种文件系统类型,每种类型对应不同的访问权限和存储位置:

// 常见的文件系统类型
plus.io.PRIVATE_WWW    // 应用资源目录,只读
plus.io.PRIVATE_DOC    // 应用私有文档目录,可读写
plus.io.PUBLIC_DOCUMENTS // 公共文档目录,可读写
plus.io.PUBLIC_DOWNLOADS // 公共下载目录,可读写

注意:从 Android 10(API 级别 29)开始,应用对公共存储区域的访问权限受到了更严格的限制。如果你的应用需要访问外部存储,需要特别注意权限适配问题。

在实际开发中,我通常根据数据的敏感性来选择存储位置:

数据类型推荐存储位置特点适用场景
应用配置文件PRIVATE_DOC应用私有,用户不可见用户设置、登录信息
用户生成的内容PUBLIC_DOCUMENTS用户可见,可备份用户文档、图片
缓存数据PRIVATE_DOC 的子目录可被系统清理临时文件、图片缓存
下载的文件PUBLIC_DOWNLOADS用户可见,系统管理用户下载的文件

1.2 核心对象关系图

理解以下几个核心对象的关系至关重要:

  1. FileSystem - 文件系统对象,通过 plus.io.requestFileSystem() 获取
  2. DirectoryEntry - 目录条目,代表文件系统中的一个目录
  3. FileEntry - 文件条目,代表文件系统中的一个文件
  4. FileWriter - 文件写入器,用于向文件写入数据
  5. FileReader - 文件读取器,用于从文件读取数据

这些对象之间的关系可以用一个简单的流程来描述:首先获取文件系统,然后获取目录或文件条目,最后通过相应的读写器进行操作。

2. 文件读写基础:从零开始构建可靠的操作函数

很多教程直接给出封装好的代码,但我觉得理解每一步的原理更重要。下面我将拆解文件读写的每一个步骤,并解释为什么要这样处理。

2.1 异步操作与 Promise 封装

5+API 的文件操作都是异步的,使用回调函数。在现代 JavaScript 开发中,我们更倾向于使用 Promise 和 async/await,这样代码更清晰,错误处理更方便。

// 基础的文件系统请求函数
function requestFileSystem(fsType) {
    return new Promise((resolve, reject) => {
        plus.io.requestFileSystem(fsType, (fs) => {
            resolve(fs);
        }, (error) => {
            reject(new Error(`请求文件系统失败: ${error.message}`));
        });
    });
}

// 获取文件条目的通用函数
function getFileEntry(fs, filePath, options = { create: false }) {
    return new Promise((resolve, reject) => {
        fs.root.getFile(filePath, options, (fileEntry) => {
            resolve(fileEntry);
        }, (error) => {
            reject(new Error(`获取文件条目失败: ${error.message}`));
        });
    });
}

这里有几个关键点需要注意:

  1. 错误信息的处理:原生的错误对象可能信息不够详细,我通常会包装一下,加上上下文信息
  2. Promise 的 reject:一定要使用 Error 对象,而不是字符串,这样能保留调用栈信息
  3. 选项参数:create 选项控制文件不存在时是否创建,根据业务需求灵活设置

2.2 完整的文件读取实现

读取文件看起来简单,但要做好错误处理和性能优化,需要考虑很多细节:

/**
 * 读取文本文件内容
 * @param {string} filePath - 文件路径(相对路径)
 * @param {string} encoding - 编码格式,默认 'utf-8'
 * @param {number} fsType - 文件系统类型,默认 plus.io.PRIVATE_DOC
 * @returns {Promise<string>} 文件内容
 */
async function readTextFile(filePath, encoding = 'utf-8', fsType = plus.io.PRIVATE_DOC) {
    try {
        // 1. 请求文件系统
        const fs = await requestFileSystem(fsType);
        
        // 2. 获取文件条目
        const fileEntry = await getFileEntry(fs, filePath, { create: false });
        
        // 3. 获取文件对象
        const file = await new Promise((resolve, reject) => {
            fileEntry.file((fileObj) => {
                resolve(fileObj);
            }, (error) => {
                reject(new Error(`获取文件对象失败: ${error.message}`));
            });
        });
        
        // 4. 读取文件内容
        const content = await new Promise((resolve, reject) => {
            const fileReader = new plus.io.FileReader();
            
            fileReader.onload = (event) => {
                resolve(event.target.result);
            };
            
            fileReader.onerror = (error) => {
                reject(new Error(`读取文件失败: ${error.message}`));
            };
            
            // 根据文件大小选择读取策略
            if (file.size > 1024 * 1024) { // 大于1MB
                console.warn(`文件较大(${(file.size / 1024 / 1024).toFixed(2)}MB),建议分片读取`);
            }
            
            fileReader.readAsText(file, encoding);
        });
        
        return content;
        
    } catch (error) {
        // 统一错误处理
        console.error(`读取文件 ${filePath} 失败:`, error);
        
        // 根据错误类型提供友好的用户提示
        if (error.message.includes('未找到文件')) {
            throw new Error(`文件 ${filePath} 不存在`);
        } else if (error.message.includes('权限')) {
            throw new Error('没有文件访问权限,请检查应用权限设置');
        } else {
            throw error;
        }
    }
}

这个实现考虑了:

  • 大文件处理:超过1MB的文件会给出警告
  • 详细的错误信息:区分不同类型的错误
  • 完整的 Promise 链:使用 async/await 让代码更清晰
  • 内存管理:及时清理不再使用的对象

2.3 文件写入的进阶技巧

写入文件比读取更复杂,因为涉及到写入位置、覆盖策略等问题:

/**
 * 写入文本文件
 * @param {string} filePath - 文件路径
 * @param {string} content - 要写入的内容
 * @param {Object} options - 写入选项
 * @param {boolean} options.append - 是否追加,默认false(覆盖)
 * @param {number} options.position - 写入位置(字节),append为true时有效
 * @param {string} options.encoding - 编码格式,默认 'utf-8'
 * @returns {Promise<void>}
 */
async function writeTextFile(filePath, content, options = {}) {
    const {
        append = false,
        position = 0,
        encoding = 'utf-8'
    } = options;
    
    try {
        const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
        const fileEntry = await getFileEntry(fs, filePath, { create: true });
        
        // 获取 FileWriter
        const writer = await new Promise((resolve, reject) => {
            fileEntry.createWriter((fileWriter) => {
                resolve(fileWriter);
            }, (error) => {
                reject(new Error(`创建文件写入器失败: ${error.message}`));
            });
        });
        
        return new Promise((resolve, reject) => {
            // 设置写入位置
            if (append) {
                writer.seek(position);
            } else {
                writer.seek(0);
                // 非追加模式,先截断文件
                writer.truncate(0);
            }
            
            writer.onwrite = () => {
                console.log(`文件 ${filePath} 写入成功`);
                resolve();
            };
            
            writer.onerror = (error) => {
                reject(new Error(`写入文件失败: ${error.message}`));
            };
            
            // 开始写入
            writer.write(content);
        });
        
    } catch (error) {
        console.error(`写入文件 ${filePath} 失败:`, error);
        
        // 检查存储空间
        if (error.message.includes('空间')) {
            throw new Error('存储空间不足,请清理后重试');
        }
        
        throw error;
    }
}

这里有几个实用的技巧:

  1. 文件截断:非追加模式时,先调用 truncate(0) 清空文件
  2. 写入位置控制:通过 seek() 方法精确控制写入位置
  3. 错误类型识别:识别存储空间不足等特定错误

3. 实战封装:构建企业级文件操作工具库

在实际项目中,我们需要的不仅仅是基础的读写功能,而是一套完整的文件操作解决方案。下面是我在多个项目中总结出来的工具库设计。

3.1 配置文件管理

很多应用需要管理配置文件,比如用户设置、应用状态等。我设计了一个配置管理器:

class ConfigManager {
    constructor(configName = 'app-config.json') {
        this.configPath = `configs/${configName}`;
        this.cache = null;
        this.lastModified = 0;
    }
    
    /**
     * 读取配置
     * @param {boolean} forceRefresh - 是否强制刷新缓存
     * @returns {Promise<Object>}
     */
    async getConfig(forceRefresh = false) {
        // 使用缓存提高性能
        if (!forceRefresh && this.cache && Date.now() - this.lastModified < 30000) {
            return this.cache;
        }
        
        try {
            const content = await readTextFile(this.configPath);
            this.cache = JSON.parse(content);
            this.lastModified = Date.now();
            return this.cache;
        } catch (error) {
            // 文件不存在,返回默认配置
            if (error.message.includes('不存在')) {
                const defaultConfig = this.getDefaultConfig();
                await this.saveConfig(defaultConfig);
                return defaultConfig;
            }
            throw error;
        }
    }
    
    /**
     * 保存配置
     * @param {Object} config - 配置对象
     * @returns {Promise<void>}
     */
    async saveConfig(config) {
        // 验证配置格式
        this.validateConfig(config);
        
        // 合并现有配置(部分更新)
        const currentConfig = await this.getConfig().catch(() => ({}));
        const mergedConfig = { ...currentConfig, ...config };
        
        // 序列化时美化格式
        const content = JSON.stringify(mergedConfig, null, 2);
        
        await writeTextFile(this.configPath, content);
        
        // 更新缓存
        this.cache = mergedConfig;
        this.lastModified = Date.now();
        
        // 触发配置变更事件
        this.emit('configChanged', mergedConfig);
    }
    
    /**
     * 获取配置项
     * @param {string} key - 配置键,支持点符号(如 'user.theme')
     * @param {any} defaultValue - 默认值
     * @returns {Promise<any>}
     */
    async getItem(key, defaultValue = null) {
        const config = await this.getConfig();
        
        // 支持嵌套属性访问
        return key.split('.').reduce((obj, k) => {
            return obj && obj[k] !== undefined ? obj[k] : defaultValue;
        }, config);
    }
    
    /**
     * 设置配置项
     * @param {string} key - 配置键
     * @param {any} value - 配置值
     * @returns {Promise<void>}
     */
    async setItem(key, value) {
        const config = await this.getConfig();
        
        // 支持嵌套属性设置
        const keys = key.split('.');
        const lastKey = keys.pop();
        const target = keys.reduce((obj, k) => {
            if (!obj[k] || typeof obj[k] !== 'object') {
                obj[k] = {};
            }
            return obj[k];
        }, config);
        
        target[lastKey] = value;
        
        await this.saveConfig(config);
    }
    
    // 其他辅助方法...
    getDefaultConfig() {
        return {
            version: '1.0.0',
            user: {
                theme: 'light',
                language: 'zh-CN'
            },
            app: {
                firstLaunch: true,
                lastVersion: ''
            }
        };
    }
    
    validateConfig(config) {
        // 配置验证逻辑
        if (typeof config !== 'object') {
            throw new Error('配置必须是对象');
        }
        // 更多验证规则...
    }
}

// 使用示例
const configManager = new ConfigManager();

// 读取配置
const config = await configManager.getConfig();
console.log('当前主题:', config.user.theme);

// 更新配置
await configManager.setItem('user.theme', 'dark');
await configManager.setItem('app.lastLaunch', new Date().toISOString());

这个配置管理器的特点:

  • 智能缓存:30秒缓存,减少文件读取次数
  • 自动创建:配置文件不存在时自动创建默认配置
  • 部分更新:支持只更新部分配置项
  • 嵌套属性:支持点符号访问嵌套属性
  • 数据验证:写入前验证数据格式

3.2 文件分片上传与断点续传

对于大文件操作,我们需要更复杂的策略。下面是一个文件分片上传的实现:

class FileUploader {
    constructor(options = {}) {
        this.chunkSize = options.chunkSize || 1024 * 1024; // 1MB
        this.maxRetries = options.maxRetries || 3;
        this.concurrentUploads = options.concurrentUploads || 3;
        this.progressCallbacks = [];
    }
    
    /**
     * 分片上传文件
     * @param {string} filePath - 本地文件路径
     * @param {string} uploadUrl - 上传地址
     * @param {Object} metadata - 文件元数据
     * @returns {Promise<string>} 文件ID或URL
     */
    async uploadFile(filePath, uploadUrl, metadata = {}) {
        // 1. 获取文件信息
        const fileInfo = await this.getFileInfo(filePath);
        
        // 2. 初始化上传会话
        const sessionId = await this.initUploadSession(uploadUrl, {
            ...metadata,
            fileName: fileInfo.name,
            fileSize: fileInfo.size,
            totalChunks: Math.ceil(fileInfo.size / this.chunkSize)
        });
        
        // 3. 读取并上传分片
        const chunks = await this.readFileChunks(filePath, fileInfo.size);
        const results = await this.uploadChunks(chunks, uploadUrl, sessionId);
        
        // 4. 完成上传
        return await this.completeUpload(uploadUrl, sessionId, results);
    }
    
    /**
     * 读取文件分片
     */
    async readFileChunks(filePath, fileSize) {
        const chunks = [];
        const totalChunks = Math.ceil(fileSize / this.chunkSize);
        
        for (let i = 0; i < totalChunks; i++) {
            const start = i * this.chunkSize;
            const end = Math.min(start + this.chunkSize, fileSize);
            
            chunks.push({
                index: i,
                start,
                end,
                size: end - start
            });
        }
        
        return chunks;
    }
    
    /**
     * 上传分片(支持并发和重试)
     */
    async uploadChunks(chunks, uploadUrl, sessionId) {
        const results = new Array(chunks.length).fill(null);
        const uploading = new Set();
        
        // 进度跟踪
        let uploadedCount = 0;
        const updateProgress = () => {
            uploadedCount++;
            const progress = (uploadedCount / chunks.length) * 100;
            this.progressCallbacks.forEach(callback => callback(progress));
        };
        
        // 并发控制
        while (results.some(result => result === null)) {
            // 找到未开始上传的分片
            const availableSlots = this.concurrentUploads - uploading.size;
            if (availableSlots <= 0) {
                await this.sleep(100);
                continue;
            }
            
            const nextChunkIndex = results.findIndex((result, index) => 
                result === null && !uploading.has(index)
            );
            
            if (nextChunkIndex === -1) break;
            
            uploading.add(nextChunkIndex);
            
            this.uploadChunkWithRetry(
                chunks[nextChunkIndex],
                uploadUrl,
                sessionId,
                nextChunkIndex
            ).then(result => {
                results[nextChunkIndex] = result;
                uploading.delete(nextChunkIndex);
                updateProgress();
            }).catch(error => {
                console.error(`分片 ${nextChunkIndex} 上传失败:`, error);
                results[nextChunkIndex] = { error: error.message };
                uploading.delete(nextChunkIndex);
            });
        }
        
        // 等待所有上传完成
        while (uploading.size > 0) {
            await this.sleep(100);
        }
        
        return results;
    }
    
    /**
     * 带重试机制的分片上传
     */
    async uploadChunkWithRetry(chunk, uploadUrl, sessionId, chunkIndex) {
        let lastError;
        
        for (let attempt = 0; attempt < this.maxRetries; attempt++) {
            try {
                // 读取分片数据
                const chunkData = await this.readChunkData(chunk);
                
                // 构建FormData
                const formData = new FormData();
                formData.append('sessionId', sessionId);
                formData.append('chunkIndex', chunkIndex);
                formData.append('chunkData', chunkData);
                
                // 上传
                const response = await uni.uploadFile({
                    url: uploadUrl,
                    filePath: chunkData,
                    name: 'chunkData',
                    formData: {
                        sessionId,
                        chunkIndex,
                        totalChunks: chunk.totalChunks
                    }
                });
                
                if (response.statusCode === 200) {
                    return JSON.parse(response.data);
                } else {
                    throw new Error(`上传失败: ${response.statusCode}`);
                }
                
            } catch (error) {
                lastError = error;
                console.warn(`分片 ${chunkIndex} 第 ${attempt + 1} 次尝试失败:`, error);
                
                if (attempt < this.maxRetries - 1) {
                    await this.sleep(1000 * Math.pow(2, attempt)); // 指数退避
                }
            }
        }
        
        throw lastError;
    }
    
    // 其他辅助方法...
    async getFileInfo(filePath) {
        // 获取文件信息的实现
    }
    
    async initUploadSession(uploadUrl, metadata) {
        // 初始化上传会话的实现
    }
    
    async readChunkData(chunk) {
        // 读取分片数据的实现
    }
    
    async completeUpload(uploadUrl, sessionId, results) {
        // 完成上传的实现
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    onProgress(callback) {
        this.progressCallbacks.push(callback);
    }
}

// 使用示例
const uploader = new FileUploader({
    chunkSize: 512 * 1024, // 512KB
    concurrentUploads: 2
});

uploader.onProgress((progress) => {
    console.log(`上传进度: ${progress.toFixed(1)}%`);
});

try {
    const fileUrl = await uploader.uploadFile(
        '/storage/emulated/0/Download/large-video.mp4',
        'https://api.example.com/upload',
        {
            type: 'video',
            description: '项目演示视频'
        }
    );
    console.log('文件上传成功:', fileUrl);
} catch (error) {
    console.error('上传失败:', error);
}

这个文件上传器的核心特性:

  • 分片上传:支持大文件分片上传
  • 断点续传:通过会话ID支持断点续传
  • 并发控制:控制同时上传的分片数量
  • 自动重试:失败时自动重试,使用指数退避策略
  • 进度反馈:实时上传进度反馈

3.3 文件系统监控与同步

对于需要实时同步文件的应用,文件系统监控是必不可少的:

class FileSystemWatcher {
    constructor(watchPath, options = {}) {
        this.watchPath = watchPath;
        this.interval = options.interval || 5000; // 检查间隔
        this.recursive = options.recursive || false;
        this.watchers = new Map(); // path -> { callback, lastModified }
        this.timer = null;
        this.fileCache = new Map();
    }
    
    /**
     * 开始监控
     */
    start() {
        if (this.timer) {
            console.warn('文件监控已启动');
            return;
        }
        
        console.log(`开始监控目录: ${this.watchPath}`);
        this.timer = setInterval(() => {
            this.checkChanges();
        }, this.interval);
    }
    
    /**
     * 停止监控
     */
    stop() {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
            console.log('停止文件监控');
        }
    }
    
    /**
     * 监控特定文件或目录
     * @param {string} path - 相对路径
     * @param {Function} callback - 变更回调
     */
    watch(path, callback) {
        const fullPath = this.normalizePath(path);
        
        this.watchers.set(fullPath, {
            callback,
            lastModified: 0,
            lastSize: 0
        });
        
        console.log(`开始监控: ${fullPath}`);
        
        // 立即检查一次当前状态
        this.checkFile(fullPath).then(fileInfo => {
            if (fileInfo) {
                this.watchers.get(fullPath).lastModified = fileInfo.lastModified;
                this.watchers.get(fullPath).lastSize = fileInfo.size;
            }
        });
        
        // 返回取消监控的函数
        return () => {
            this.unwatch(path);
        };
    }
    
    /**
     * 取消监控
     */
    unwatch(path) {
        const fullPath = this.normalizePath(path);
        if (this.watchers.has(fullPath)) {
            this.watchers.delete(fullPath);
            console.log(`停止监控: ${fullPath}`);
        }
    }
    
    /**
     * 检查文件变更
     */
    async checkChanges() {
        const checkPromises = [];
        
        for (const [path, watcher] of this.watchers) {
            checkPromises.push(this.checkFile(path).then(fileInfo => {
                if (!fileInfo) {
                    // 文件被删除
                    if (watcher.lastModified > 0) {
                        watcher.callback('deleted', null, path);
                        watcher.lastModified = 0;
                        watcher.lastSize = 0;
                    }
                    return;
                }
                
                // 检查变更
                const changed = 
                    fileInfo.lastModified !== watcher.lastModified ||
                    fileInfo.size !== watcher.lastSize;
                
                if (changed) {
                    const changeType = watcher.lastModified === 0 ? 'created' : 'modified';
                    
                    watcher.callback(changeType, fileInfo, path);
                    
                    watcher.lastModified = fileInfo.lastModified;
                    watcher.lastSize = fileInfo.size;
                }
            }));
        }
        
        await Promise.allSettled(checkPromises);
    }
    
    /**
     * 检查单个文件
     */
    async checkFile(path) {
        try {
            const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
            const fileEntry = await getFileEntry(fs, path, { create: false });
            
            const metadata = await new Promise((resolve, reject) => {
                fileEntry.getMetadata(resolve, reject);
            });
            
            const file = await new Promise((resolve, reject) => {
                fileEntry.file(resolve, reject);
            });
            
            return {
                path,
                lastModified: metadata.modificationTime.getTime(),
                size: file.size,
                type: file.type,
                name: file.name
            };
            
        } catch (error) {
            // 文件不存在
            if (error.message.includes('未找到')) {
                return null;
            }
            console.error(`检查文件 ${path} 失败:`, error);
            return null;
        }
    }
    
    /**
     * 规范化路径
     */
    normalizePath(path) {
        // 移除开头的斜杠,确保相对路径
        return path.replace(/^\//, '');
    }
    
    /**
     * 批量监控目录下的所有文件
     */
    async watchDirectory(dirPath, callback, fileFilter = null) {
        const files = await this.listFiles(dirPath, this.recursive);
        
        for (const file of files) {
            if (fileFilter && !fileFilter(file)) {
                continue;
            }
            
            this.watch(file.fullPath, (changeType, fileInfo) => {
                callback(changeType, fileInfo, file.fullPath);
            });
        }
        
        // 返回取消所有监控的函数
        return () => {
            for (const file of files) {
                this.unwatch(file.fullPath);
            }
        };
    }
    
    /**
     * 列出目录下的所有文件
     */
    async listFiles(dirPath, recursive = false) {
        const results = [];
        
        try {
            const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
            const dirEntry = await this.getDirectoryEntry(fs, dirPath);
            
            const reader = dirEntry.createReader();
            const entries = await new Promise((resolve, reject) => {
                reader.readEntries(resolve, reject);
            });
            
            for (const entry of entries) {
                if (entry.isFile) {
                    results.push({
                        name: entry.name,
                        fullPath: entry.fullPath,
                        isFile: true,
                        isDirectory: false
                    });
                } else if (entry.isDirectory && recursive) {
                    // 递归处理子目录
                    const subFiles = await this.listFiles(entry.fullPath, true);
                    results.push(...subFiles);
                }
            }
            
        } catch (error) {
            console.error(`列出目录 ${dirPath} 失败:`, error);
        }
        
        return results;
    }
    
    async getDirectoryEntry(fs, dirPath) {
        return new Promise((resolve, reject) => {
            fs.root.getDirectory(dirPath, { create: false }, resolve, reject);
        });
    }
}

// 使用示例
const watcher = new FileSystemWatcher('documents', {
    interval: 3000, // 每3秒检查一次
    recursive: true  // 递归监控子目录
});

// 开始监控
watcher.start();

// 监控特定文件
const unwatchConfig = watcher.watch('config/app-settings.json', (changeType, fileInfo) => {
    console.log(`配置文件 ${changeType}:`, fileInfo);
    
    if (changeType === 'modified') {
        // 重新加载配置
        this.reloadConfig();
    }
});

// 监控整个目录
const unwatchAll = await watcher.watchDirectory('user-data', (changeType, fileInfo, path) => {
    console.log(`文件变更 [${changeType}]: ${path}`);
    
    // 同步到云端
    if (changeType === 'modified' || changeType === 'created') {
        this.syncToCloud(path);
    }
});

// 停止监控(在组件销毁时调用)
// unwatchConfig();
// unwatchAll();
// watcher.stop();

文件系统监控器的设计考虑:

  • 性能优化:使用缓存减少文件系统访问
  • 变更检测:通过修改时间和文件大小检测变更
  • 递归监控:支持监控整个目录树
  • 精确控制:可以监控特定文件或整个目录
  • 资源清理:提供取消监控的方法

4. 性能优化与最佳实践

在实际项目中,文件操作的性能直接影响用户体验。下面分享一些我在实践中总结的优化技巧。

4.1 文件操作性能优化

批量操作减少IO次数

class BatchFileProcessor {
    constructor() {
        this.operations = [];
        this.batchSize = 10; // 每批处理10个操作
    }
    
    /**
     * 添加读取操作
     */
    addReadOperation(filePath, callback) {
        this.operations.push({
            type: 'read',
            path: filePath,
            callback
        });
    }
    
    /**
     * 添加写入操作
     */
    addWriteOperation(filePath, content, callback) {
        this.operations.push({
            type: 'write',
            path: filePath,
            content,
            callback
        });
    }
    
    /**
     * 执行批量操作
     */
    async execute() {
        const results = [];
        const batches = this.chunkArray(this.operations, this.batchSize);
        
        for (let i = 0; i < batches.length; i++) {
            const batch = batches[i];
            console.log(`处理第 ${i + 1}/${batches.length} 批,共 ${batch.length} 个操作`);
            
            const batchResults = await Promise.allSettled(
                batch.map(op => this.processOperation(op))
            );
            
            results.push(...batchResults);
            
            // 批之间短暂延迟,避免阻塞UI
            if (i < batches.length - 1) {
                await this.sleep(50);
            }
        }
        
        return results;
    }
    
    /**
     * 处理单个操作
     */
    async processOperation(operation) {
        try {
            let result;
            
            switch (operation.type) {
                case 'read':
                    result = await readTextFile(operation.path);
                    break;
                case 'write':
                    await writeTextFile(operation.path, operation.content);
                    result = { success: true };
                    break;
                default:
                    throw new Error(`未知操作类型: ${operation.type}`);
            }
            
            if (operation.callback) {
                operation.callback(null, result);
            }
            
            return { status: 'fulfilled', value: result };
            
        } catch (error) {
            if (operation.callback) {
                operation.callback(error, null);
            }
            
            return { status: 'rejected', reason: error };
        }
    }
    
    /**
     * 数组分块
     */
    chunkArray(array, size) {
        const chunks = [];
        for (let i = 0; i < array.length; i += size) {
            chunks.push(array.slice(i, i + size));
        }
        return chunks;
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 使用示例
const processor = new BatchFileProcessor();

// 添加多个读取操作
processor.addReadOperation('config/user.json', (error, content) => {
    if (!error) {
        console.log('读取用户配置成功');
    }
});

processor.addReadOperation('data/cache.json', (error, content) => {
    if (!error) {
        console.log('读取缓存数据成功');
    }
});

// 添加写入操作
processor.addWriteOperation('logs/app.log', '新的日志条目\n', (error) => {
    if (!error) {
        console.log('写入日志成功');
    }
});

// 执行批量操作
const results = await processor.execute();
console.log(`批量操作完成,成功: ${results.filter(r => r.status === 'fulfilled').length}, 失败: ${results.filter(r => r.status === 'rejected').length}`);

内存使用优化

对于大文件操作,需要注意内存使用:

class StreamFileProcessor {
    /**
     * 流式读取大文件
     */
    async readLargeFileStream(filePath, chunkSize = 64 * 1024, onChunk) {
        const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
        const fileEntry = await getFileEntry(fs, filePath, { create: false });
        
        const file = await new Promise((resolve, reject) => {
            fileEntry.file(resolve, reject);
        });
        
        const totalSize = file.size;
        let position = 0;
        
        while (position < totalSize) {
            const chunk = await this.readFileChunk(fileEntry, position, chunkSize);
            
            if (onChunk) {
                const shouldContinue = await onChunk(chunk, position, totalSize);
                if (shouldContinue === false) {
                    break;
                }
            }
            
            position += chunk.length;
            
            // 定期释放内存
            if (position % (10 * chunkSize) === 0) {
                await this.sleep(0); // 让出控制权
            }
        }
        
        return { totalSize, processed: position };
    }
    
    /**
     * 读取文件块
     */
    async readFileChunk(fileEntry, position, size) {
        return new Promise((resolve, reject) => {
            fileEntry.file((file) => {
                const slice = file.slice(position, position + size);
                const reader = new plus.io.FileReader();
                
                reader.onload = (event) => {
                    resolve(event.target.result);
                };
                
                reader.onerror = reject;
                
                reader.readAsArrayBuffer(slice);
            }, reject);
        });
    }
    
    /**
     * 流式写入大文件
     */
    async writeLargeFileStream(filePath, dataGenerator, chunkSize = 64 * 1024) {
        const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
        const fileEntry = await getFileEntry(fs, filePath, { create: true });
        
        const writer = await new Promise((resolve, reject) => {
            fileEntry.createWriter(resolve, reject);
        });
        
        // 清空文件
        writer.truncate(0);
        writer.seek(0);
        
        let position = 0;
        let chunk;
        
        while ((chunk = await dataGenerator()) !== null) {
            // 确保chunk不超过指定大小
            const chunks = this.splitChunk(chunk, chunkSize);
            
            for (const subChunk of chunks) {
                await new Promise((resolve, reject) => {
                    writer.onwrite = resolve;
                    writer.onerror = reject;
                    writer.write(subChunk);
                });
                
                position += subChunk.length;
                
                // 定期保存进度
                if (position % (10 * chunkSize) === 0) {
                    this.saveProgress(filePath, position);
                }
            }
            
            // 释放内存
            chunk = null;
            await this.sleep(0);
        }
        
        return { totalSize: position };
    }
    
    splitChunk(data, maxSize) {
        const chunks = [];
        for (let i = 0; i < data.length; i += maxSize) {
            chunks.push(data.slice(i, i + maxSize));
        }
        return chunks;
    }
    
    saveProgress(filePath, position) {
        // 保存进度到临时文件
        const progressPath = `${filePath}.progress`;
        writeTextFile(progressPath, position.toString()).catch(() => {});
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 使用示例:流式处理大文件
const processor = new StreamFileProcessor();

// 流式读取
await processor.readLargeFileStream('large-data.bin', 1024 * 1024, async (chunk, position, totalSize) => {
    console.log(`处理进度: ${((position / totalSize) * 100).toFixed(1)}%`);
    
    // 处理数据块
    await processChunk(chunk);
    
    // 返回true继续,false停止
    return true;
});

// 流式写入
const dataGenerator = async () => {
    // 生成数据块,返回null表示结束
    if (noMoreData) {
        return null;
    }
    return await generateNextChunk();
};

await processor.writeLargeFileStream('output.bin', dataGenerator);

4.2 错误处理与恢复

健壮的文件操作需要完善的错误处理和恢复机制:

class ResilientFileManager {
    constructor() {
        this.retryStrategies = {
            'ENOENT': { // 文件不存在
                maxRetries: 1,
                action: 'create',
                delay: 0
            },
            'EACCES': { // 权限错误
                maxRetries: 2,
                action: 'wait',
                delay: 1000
            },
            'ENOSPC': { // 空间不足
                maxRetries: 1,
                action: 'cleanup',
                delay: 0
            },
            'default': {
                maxRetries: 3,
                action: 'retry',
                delay: 1000
            }
        };
        
        this.errorStats = new Map();
    }
    
    /**
     * 带重试的文件操作
     */
    async withRetry(operation, context = {}) {
        let lastError;
        let retryCount = 0;
        
        const strategy = this.getRetryStrategy(context.errorType);
        
        while (retryCount <= strategy.maxRetries) {
            try {
                if (retryCount > 0) {
                    console.log(`第 ${retryCount} 次重试 ${context.operationName || '操作'}`);
                    
                    // 执行重试前的操作
                    await this.beforeRetry(strategy.action, context);
                    
                    // 延迟重试
                    if (strategy.delay > 0) {
                        await this.sleep(strategy.delay * Math.pow(2, retryCount - 1));
                    }
                }
                
                return await operation();
                
            } catch (error) {
                lastError = error;
                retryCount++;
                
                // 记录错误统计
                this.recordError(error, context);
                
                // 检查是否应该继续重试
                if (!this.shouldRetry(error, retryCount, strategy)) {
                    break;
                }
                
                // 更新错误类型
                context.errorType = this.getErrorType(error);
            }
        }
        
        // 所有重试都失败
        throw this.wrapError(lastError, retryCount, context);
    }
    
    /**
     * 获取重试策略
     */
    getRetryStrategy(errorType) {
        return this.retryStrategies[errorType] || this.retryStrategies.default;
    }
    
    /**
     * 重试前的操作
     */
    async beforeRetry(action, context) {
        switch (action) {
            case 'create':
                // 创建必要的目录
                await this.ensureDirectory(context.filePath);
                break;
                
            case 'cleanup':
                // 清理缓存
                await this.cleanupCache();
                break;
                
            case 'wait':
                // 等待系统资源
                await this.waitForSystem();
                break;
                
            case 'retry':
                // 普通重试,不做特殊操作
                break;
        }
    }
    
    /**
     * 判断是否应该重试
     */
    shouldRetry(error, retryCount, strategy) {
        // 超过最大重试次数
        if (retryCount > strategy.maxRetries) {
            return false;
        }
        
        // 特定错误不重试
        const errorMessage = error.message.toLowerCase();
        if (errorMessage.includes('格式错误') || 
            errorMessage.includes('不支持')) {
            return false;
        }
        
        // 检查错误频率
        const errorKey = this.getErrorKey(error);
        const errorCount = this.errorStats.get(errorKey) || 0;
        
        if (errorCount > 5) { // 相同错误出现太多次
            return false;
        }
        
        return true;
    }
    
    /**
     * 包装错误信息
     */
    wrapError(error, retryCount, context) {
        const wrappedError = new Error(
            `${context.operationName || '操作'}失败(重试 ${retryCount - 1} 次): ${error.message}`
        );
        
        wrappedError.originalError = error;
        wrappedError.retryCount = retryCount - 1;
        wrappedError.context = context;
        wrappedError.timestamp = new Date().toISOString();
        
        // 保存错误日志
        this.saveErrorLog(wrappedError);
        
        return wrappedError;
    }
    
    /**
     * 确保目录存在
     */
    async ensureDirectory(filePath) {
        const dirPath = filePath.substring(0, filePath.lastIndexOf('/'));
        
        if (!dirPath) {
            return;
        }
        
        try {
            const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
            
            await new Promise((resolve, reject) => {
                fs.root.getDirectory(dirPath, { create: true }, resolve, reject);
            });
            
        } catch (error) {
            console.warn(`创建目录失败: ${dirPath}`, error);
        }
    }
    
    /**
     * 清理缓存
     */
    async cleanupCache() {
        const cacheDir = 'cache/';
        const maxCacheSize = 100 * 1024 * 1024; // 100MB
        
        try {
            const files = await this.listFiles(cacheDir);
            let totalSize = 0;
            
            // 计算缓存大小
            for (const file of files) {
                totalSize += file.size || 0;
            }
            
            // 如果超过限制,清理最旧的文件
            if (totalSize > maxCacheSize) {
                files.sort((a, b) => a.lastModified - b.lastModified);
                
                for (const file of files) {
                    if (totalSize <= maxCacheSize * 0.8) { // 清理到80%
                        break;
                    }
                    
                    await this.deleteFile(file.fullPath);
                    totalSize -= file.size;
                    
                    console.log(`清理缓存文件: ${file.name}`);
                }
            }
            
        } catch (error) {
            console.warn('清理缓存失败:', error);
        }
    }
    
    /**
     * 记录错误统计
     */
    recordError(error, context) {
        const errorKey = this.getErrorKey(error);
        const count = this.errorStats.get(errorKey) || 0;
        this.errorStats.set(errorKey, count + 1);
        
        // 定期清理旧的统计
        if (this.errorStats.size > 100) {
            const keys = Array.from(this.errorStats.keys());
            for (let i = 0; i < 20; i++) {
                this.errorStats.delete(keys[i]);
            }
        }
    }
    
    getErrorKey(error) {
        return `${error.code || 'NO_CODE'}:${error.message.substring(0, 50)}`;
    }
    
    getErrorType(error) {
        const message = error.message.toLowerCase();
        
        if (message.includes('no such file') || message.includes('未找到')) {
            return 'ENOENT';
        } else if (message.includes('permission') || message.includes('权限')) {
            return 'EACCES';
        } else if (message.includes('no space') || message.includes('空间')) {
            return 'ENOSPC';
        }
        
        return 'default';
    }
    
    async saveErrorLog(error) {
        const logEntry = {
            timestamp: error.timestamp,
            message: error.message,
            originalMessage: error.originalError?.message,
            retryCount: error.retryCount,
            context: error.context,
            stack: error.stack
        };
        
        const logPath = `logs/errors/${new Date().toISOString().split('T')[0]}.json`;
        
        try {
            // 读取现有日志
            let logs = [];
            try {
                const content = await readTextFile(logPath);
                logs = JSON.parse(content);
            } catch (e) {
                // 文件不存在,使用空数组
            }
            
            // 添加新日志
            logs.push(logEntry);
            
            // 限制日志数量
            if (logs.length > 1000) {
                logs = logs.slice(-500);
            }
            
            // 保存
            await writeTextFile(logPath, JSON.stringify(logs, null, 2));
            
        } catch (e) {
            console.error('保存错误日志失败:', e);
        }
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    // 其他辅助方法...
    async listFiles(dirPath) {
        // 实现文件列表获取
    }
    
    async deleteFile(filePath) {
        // 实现文件删除
    }
    
    async waitForSystem() {
        // 等待系统资源
        await this.sleep(1000);
    }
}

// 使用示例
const resilientManager = new ResilientFileManager();

// 使用重试机制进行文件操作
try {
    const content = await resilientManager.withRetry(
        async () => {
            return await readTextFile('important-data.json');
        },
        {
            operationName: '读取重要数据',
            filePath: 'important-data.json'
        }
    );
    
    console.log('数据读取成功:', content);
    
} catch (error) {
    console.error('最终失败:', error);
    
    // 根据错误类型采取不同措施
    if (error.originalError?.message.includes('空间不足')) {
        // 提示用户清理空间
        uni.showModal({
            title: '存储空间不足',
            content: '请清理存储空间后重试',
            showCancel: false
        });
    }
}

4.3 平台兼容性处理

不同平台(Android、iOS)的文件系统有差异,需要进行兼容性处理:

class PlatformFileAdapter {
    constructor() {
        this.platform = uni.getSystemInfoSync().platform;
        this.isAndroid = this.platform === 'android';
        this.isIOS = this.platform === 'ios';
        
        this.platformSpecificPaths = {
            android: {
                publicDocuments: plus.io.PUBLIC_DOCUMENTS,
                privateDocs: plus.io.PRIVATE_DOC,
                downloads: plus.io.PUBLIC_DOWNLOADS,
                externalStorage: 'file:///storage/emulated/0/'
            },
            ios: {
                publicDocuments: plus.io.PUBLIC_DOCUMENTS,
                privateDocs: plus.io.PRIVATE_DOC,
                downloads: plus.io.PUBLIC_DOCUMENTS + '/Downloads/',
                // iOS 没有直接的外部存储访问
                externalStorage: null
            }
        };
    }
    
    /**
     * 获取平台适配的文件路径
     */
    getAdaptedPath(originalPath, options = {}) {
        const {
            accessLevel = 'private', // private, public, external
            ensureExists = false
        } = options;
        
        let adaptedPath = originalPath;
        
        // 处理路径分隔符
        if (this.isAndroid) {
            adaptedPath = adaptedPath.replace(/\\/g, '/');
        } else if (this.isIOS) {
            adaptedPath = adaptedPath.replace(/\\/g, '/');
        }
        
        // 根据访问级别调整路径
        switch (accessLevel) {
            case 'public':
                adaptedPath = this.getPublicPath(adaptedPath);
                break;
                
            case 'external':
                if (this.isAndroid) {
                    adaptedPath = this.getExternalPath(adaptedPath);
                } else {
                    console.warn('iOS不支持外部存储访问,使用公共目录代替');
                    adaptedPath = this.getPublicPath(adaptedPath);
                }
                break;
                
            case 'private':
            default:
                adaptedPath = this.getPrivatePath(adaptedPath);
                break;
        }
        
        // 确保目录存在
        if (ensureExists) {
            this.ensureDirectoryExists(adaptedPath).catch(() => {
                // 静默失败,在具体操作时再处理
            });
        }
        
        return adaptedPath;
    }
    
    /**
     * 获取私有路径
     */
    getPrivatePath(relativePath) {
        const basePath = this.platformSpecificPaths[this.platform].privateDocs;
        return this.joinPath(basePath, relativePath);
    }
    
    /**
     * 获取公共路径
     */
    getPublicPath(relativePath) {
        const basePath = this.platformSpecificPaths[this.platform].publicDocuments;
        return this.joinPath(basePath, relativePath);
    }
    
    /**
     * 获取外部存储路径(仅Android)
     */
    getExternalPath(relativePath) {
        if (!this.isAndroid) {
            throw new Error('外部存储仅支持Android平台');
        }
        
        // 检查Android版本
        const systemInfo = uni.getSystemInfoSync();
        const androidVersion = parseInt(systemInfo.system.split(' ')[1]) || 0;
        
        // Android 10+ 限制外部存储访问
        if (androidVersion >= 10) {
            console.warn('Android 10+ 限制外部存储访问,使用公共目录代替');
            return this.getPublicPath(relativePath);
        }
        
        const basePath = this.platformSpecificPaths.android.externalStorage;
        return this.joinPath(basePath, relativePath);
    }
    
    /**
     * 连接路径
     */
    joinPath(base, relative) {
        if (!base) return relative;
        if (!relative) return base;
        
        const baseEndsWithSlash = base.endsWith('/');
        const relativeStartsWithSlash = relative.startsWith('/');
        
        if (baseEndsWithSlash && relativeStartsWithSlash) {
            return base + relative.substring(1);
        } else if (!baseEndsWithSlash && !relativeStartsWithSlash) {
            return base + '/' + relative;
        } else {
            return base + relative;
        }
    }
    
    /**
     * 确保目录存在
     */
    async ensureDirectoryExists(filePath) {
        const dirPath = filePath.substring(0, filePath.lastIndexOf('/'));
        
        if (!dirPath || dirPath === filePath) {
            return; // 已经是根目录或没有目录部分
        }
        
        try {
            const fs = await requestFileSystem(plus.io.PRIVATE_DOC);
            
            // 递归创建目录
            const parts = dirPath.split('/').filter(p => p);
            let currentPath = '';
            
            for (const part of parts) {
                currentPath = currentPath ? `${currentPath}/${part}` : part;
                
                await new Promise((resolve, reject) => {
                    fs.root.getDirectory(currentPath, { create: true }, resolve, reject);
                });
            }
            
        } catch (error) {
            console.warn(`创建目录失败: ${dirPath}`, error);
            throw error;
        }
    }
    
    /**
     * 检查文件访问权限
     */
    async checkPermission(filePath, operation = 'read') {
        if (this.isIOS) {
            // iOS 权限检查
            return this.checkIOSPermission(filePath, operation);
        } else if (this.isAndroid) {
            // Android 权限检查
            return this.checkAndroidPermission(filePath, operation);
        }
        
        return true; // 其他平台默认允许
    }
    
    /**
     * iOS 权限检查
     */
    async checkIOSPermission(filePath, operation) {
        // iOS 的文件权限主要受沙盒限制
        // 检查是否在沙盒允许的目录内
        
        const allowedPaths = [
            this.getPrivatePath(''),
            this.getPublicPath('')
        ];
        
        const isAllowed = allowedPaths.some(allowedPath => 
            filePath.startsWith(allowedPath)
        );
        
        if (!isAllowed) {
            throw new Error(`iOS不允许访问路径: ${filePath}`);
        }
        
        return true;
    }
    
    /**
     * Android 权限检查
     */
    async checkAndroidPermission(filePath, operation) {
        const systemInfo = uni.getSystemInfoSync();
        const androidVersion = parseInt(systemInfo.system.split(' ')[1]) || 0;
        
        // 检查是否在外部存储
        const isExternal = filePath.startsWith('file:///storage/');
        
        if (isExternal && androidVersion >= 11) {
            // Android 11+ 需要特殊权限
            const hasPermission = await this.requestAndroidStoragePermission();
            if (!hasPermission) {
                throw new Error('需要存储权限才能访问外部文件');
            }
        }
        
        return true;
    }
    
    /**
     * 请求Android存储权限
     */
    async requestAndroidStoragePermission() {
        return new Promise((resolve) => {
            // 使用5+API的权限请求
            plus.android.requestPermissions(
                ['android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE'],
                (result) => {
                    const granted = result.granted && result.granted.length === 2;
                    resolve(granted);
                },
                (error) => {
                    console.error('请求权限失败:', error);
                    resolve(false);
                }
            );
        });
    }
    
    /**
     * 获取平台特定的文件操作选项
     */
    getPlatformOptions(options = {}) {
        const platformOptions = { ...options };
        
        if (this.isIOS) {
            // iOS 特定选项
            platformOptions.encoding = options.encoding || 'utf-8';
            platformOptions.noBackup = options.noBackup !== false; // 默认不备份到iCloud
            
            if (platformOptions.noBackup) {
                // 设置文件不备份到iCloud
                this.setFileNoBackupFlag(options.filePath).catch(() => {
                    // 静默失败
                });
            }
            
        } else if (this.isAndroid) {
            // Android 特定选项
            platformOptions.encoding = options.encoding || 'utf-8';
            
            // Android 7+ 需要FileProvider
            const systemInfo = uni.getSystemInfoSync();
            const androidVersion = parseInt(systemInfo.system.split(' ')[1]) || 0;
            
            if (androidVersion >= 7 && options.shareable) {
                platformOptions.useFileProvider = true;
            }
        }
        
        return platformOptions;
    }
    
    /**
     * 设置文件不备份到iCloud(iOS)
     */
    async setFileNoBackupFlag(filePath) {
        if (!this.isIOS) {
            return;
        }
        
        try {
            // 使用5+API设置文件属性
            plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
                // iOS特定的属性设置
                // 实际实现可能需要调用原生插件
                console.log('设置文件不备份标志:', filePath);
            }, (error) => {
                console.warn('设置文件属性失败:', error);
            });
            
        } catch (error) {
            console.warn('设置iCloud备份标志失败:', error);
        }
    }
    
    /**
     * 获取平台推荐的文件大小限制
     */
    getRecommendedSizeLimit(operationType) {
        const limits = {
            android: {
                read: 100 * 1024 * 1024, // 100MB
                write: 50 * 1024 * 1024,  // 50MB
                memory: 256 * 1024 * 1024 // 256MB
            },
            ios: {
                read: 50 * 1024 * 1024,  // 50MB
                write: 25 * 1024 * 1024,  // 25MB
                memory: 128 * 1024 * 1024 // 128MB
            }
        };
        
        const platformLimits = this.isAndroid ? limits.android : limits.ios;
        return platformLimits[operationType] || platformLimits.memory;
    }
    
    /**
     * 检查文件大小是否安全
     */
    isFileSizeSafe(fileSize, operationType) {
        const limit = this.getRecommendedSizeLimit(operationType);
        return fileSize <= limit;
    }
}

// 使用示例
const fileAdapter = new PlatformFileAdapter();

// 获取适配后的路径
const privateConfigPath = fileAdapter.getAdaptedPath('config/settings.json', {
    accessLevel: 'private',
    ensureExists: true
});

const publicImagePath = fileAdapter.getAdaptedPath('images/avatar.jpg', {
    accessLevel: 'public'
});

// 检查权限
try {
    const hasPermission = await fileAdapter.checkPermission(publicImagePath, 'write');
    
    if (hasPermission) {
        // 执行文件操作
        const options = fileAdapter.getPlatformOptions({
            encoding: 'utf-8',
            shareable: true
        });
        
        // 检查文件大小
        const fileInfo = await getFileInfo(publicImagePath);
        if (fileAdapter.isFileSizeSafe(fileInfo.size, 'write')) {
            await writeTextFile(publicImagePath, imageData, options);
        } else {
            console.warn('文件过大,建议分片处理');
        }
    }
    
} catch (error) {
    console.error('文件操作失败:', error);
    
    // 根据平台提供不同的错误处理
    if (fileAdapter.isIOS && error.message.includes('iCloud')) {
        // 处理iCloud相关错误
    } else if (fileAdapter.isAndroid && error.message.includes('权限')) {
        // 处理权限错误
    }
}

这个平台适配器的主要功能:

  1. 路径适配:根据不同平台调整文件路径
  2. 权限管理:检查和处理平台特定的权限要求
  3. 大小限制:提供平台推荐的文件大小限制
  4. 特殊处理:处理iCloud备份、FileProvider等平台特定需求
  5. 错误处理:提供平台特定的错误处理建议

通过这样的平台适配,可以大大减少跨平台开发中的兼容性问题,让文件操作代码更加健壮和可靠。

更多推荐