You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:29:51