Mobile Safari中FileReader处理大量文件后返回无效数据问题问询
Mobile Safari批量选择图片超阈值读取失败的问题分析与解决
问题确认:这是Apple未公开的资源限制
你遇到的现象是Mobile Safari对input[type=file]批量选择媒体文件的未公开阈值限制,不同设备的阈值(你测试的是350张)会因内存、系统版本差异而变化。超过阈值后,Safari无法正确获取后续文件的真实数据,返回的空plist是系统层面的占位归档数据,且FileReader不会触发错误事件——因为系统认为返回了"有效"的归档结构。
无数量限制的解决办法
核心思路是拆分读取任务,控制并发数,给系统留出资源回收时间,避免一次性占用过多资源导致后续文件读取失败:
1. 控制文件读取并发数
不要一次性启动所有FileReader实例,用队列限制同时处理的文件数量(建议5-10个,可根据设备性能调整),完成一批再启动下一批:
document.getElementById('fileInput').addEventListener('change', async function(event) { const files = Array.from(event.target.files); const concurrency = 8; // 并发数可按需调整 var savedOutput = false; // 分批处理文件 for (let i = 0; i < files.length; i += concurrency) { const batch = files.slice(i, i + concurrency); await Promise.all(batch.map(file => testImage(file, savedOutput))); // 给系统留资源回收时间 await new Promise(resolve => setTimeout(resolve, 100)); } }); // 改造testImage为异步函数 async function testImage(file, savedOutputRef) { return new Promise((resolve) => { var _reader = new window.FileReader(), _fn = file.slice || file.mozSlice || file.webkitSlice || function() { return; }, _slice = _fn.call(file, 0, 3); _reader.onloadend = function(_evt) { var _isValid = false, _bytes; if (!_evt.currentTarget.error) { _bytes = new window.Uint8Array(_evt.currentTarget.result); // 图片格式校验 if ( (_bytes[0] == 0xFF && _bytes[1] == 0xD8 && _bytes[2] == 0xFF) || // JPG (_bytes[0] == 0x42 && _bytes[1] == 0x4D) || // BMP (_bytes[0] == 0x89 && _bytes[1] == 0x50 && _bytes[2] == 0x4E) // PNG ) { _isValid = true; } if (_bytes[0] == 0x00 && _bytes[1] == 0x00 && _bytes[2] == 0x00) { // HEIC _isValid = true; } } console.log(`File: ${file.name} is valid: ${_isValid}`); if (!savedOutputRef && !_isValid) { savedOutputRef = true; const decoder = new TextDecoder(); const text = decoder.decode(_bytes); console.log('Failed Output: ', text); } resolve(); } _reader.readAsArrayBuffer(file); }); }
2. 及时释放资源
处理完文件后,主动解除对File对象的引用,帮助浏览器回收内存:
// 批量处理完成后清空引用 batch.forEach(file => { /* 处理逻辑 */ }); batch.fill(null);
3. 备选方案:File System Access API(iOS 15.4+)
如果目标用户使用iOS 15.4及以上版本,可使用File System Access API替代传统input选择,它能更高效地访问本地文件,规避批量选择的阈值限制:
async function selectPhotos() { try { const handles = await window.showOpenFilePicker({ multiple: true, types: [{ description: 'Images', accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.heic'] } }] }); for (const handle of handles) { const file = await handle.getFile(); await testImage(file); } } catch (err) { console.error('File picker error:', err); } }
优化后的完整测试代码
<html> <head> <title>Tester</title> </head> <body> <button id="selectButton">Select Photos</button> <input type="file" id="fileInput" style="display: none;" accept="image/*" multiple> <script> document.getElementById('selectButton').addEventListener('click', function() { document.getElementById('fileInput').click(); }); document.getElementById('fileInput').addEventListener('change', async function(event) { const files = Array.from(event.target.files); const concurrency = 8; var savedOutput = false; for (let i = 0; i < files.length; i += concurrency) { const batch = files.slice(i, i + concurrency); await Promise.all(batch.map(file => testImage(file, savedOutput))); await new Promise(resolve => setTimeout(resolve, 100)); } }); async function testImage(file, savedOutputRef) { return new Promise((resolve) => { var _reader = new window.FileReader(), _fn = file.slice || file.mozSlice || file.webkitSlice || function() { return; }, _slice = _fn.call(file, 0, 3); _reader.onloadend = function(_evt) { var _isValid = false, _bytes; if (!_evt.currentTarget.error) { _bytes = new window.Uint8Array(_evt.currentTarget.result); if ( (_bytes[0] == 0xFF && _bytes[1] == 0xD8 && _bytes[2] == 0xFF) || (_bytes[0] == 0x42 && _bytes[1] == 0x4D) || (_bytes[0] == 0x89 && _bytes[1] == 0x50 && _bytes[2] == 0x4E) ) { _isValid = true; } if (_bytes[0] == 0x00 && _bytes[1] == 0x00 && _bytes[2] == 0x00) { _isValid = true; } } console.log(`File: ${file.name} is valid: ${_isValid}`); if (!savedOutputRef && !_isValid) { savedOutputRef = true; const decoder = new TextDecoder(); const text = decoder.decode(_bytes); console.log('Failed Output: ', text); } resolve(); } _reader.readAsArrayBuffer(file); }); } </script> </body> </html>
内容的提问来源于stack exchange,提问作者btxios
相关产品推荐
相关产品推荐

