包含数千个文件的 DownloadFile 阻塞了应用程序界面

DownloadFile with thousands of files is blocking the application interface

我正在使用此代码下载 15019 张 JPG 格式的图片:

for (const item of requestUrl) {
    const optios = {
        fromUrl: item.urlPath,
        toFile: item.path,
    };
    downloadFile(optios).promise;
}

❌ 应用界面被屏蔽

注意重要:我知道RN只在一个线程上运行,所以我在前台和后台使用运行实现了Headless JS,它可以工作,但是同样的行为,接口被阻塞了。

谁能帮我解决这个问题?


react-native-fs": "2.13.3

本机反应:0.59.9

你在使用某种状态管理吗? Redux,上下文,redux-saga/redux-thunk? 您可以使用react-native-background-downloader尝试后台下载

如果您使用的是 redux 和 redux-saga,您可以使用 fork 下载它,这样它就不会阻塞。

此外,如果您正在下载图像并将其存储在 props 或 state 中,它可能会为每个下载的图像重新渲染。

react-native-fs 在其原生实现中完成所有工作,并通过 react-native 桥公开异步 api。因此,在通过 react-native-js 下载文件时,你的 js 线程大部分时间都是空闲的,你的 js 是在前台还是后台 运行ning 并不重要。

实际问题

您实施的实际问题是 downloadFile 会立即触发下载和 return Promise。因此,您的 for 循环将 运行 几乎立即完成所有 15k 次迭代,在您的应用程序的本机部分开始 15k 并发下载。这可能足以使整个应用程序被阻止的任何设备变慢。

解决方案

如果您需要下载很多文件,您应该实施某种队列和流水线来限制并发下载的数量。 (即使设备可以毫无问题地处理 15k 并发下载,下载也会受到可用带宽的限制。同时进行约 3 次下载可能足以一直使用最大带宽。)

可能的流水线解决方案如下所示:

(以下代码未 运行 或测试)


/**
 * 
 * @param {item} The item to download
 * @param {item.urlPath} The url to download the item from
 * @param {item.path} The local path to download the item to
 * @returns the Promise returned by react-native-fs downloadFile
 */
const downloadItem = ({ urlPath, path }) => {
  const options = {
    fromUrl: urlPath,
    toFile: path
  }
  return downloadFile(options);
}

/**
 * 
 * @param {Array} Array of the items to download
 * @param {integer} maximum allowed concurrent downloads
 * @returns {Promise} which resolves when all downloads are done
 */
const downloadPipelined = (items, maxConcurrency = 3) => {
    // Clone the items Array because we will mutate the queue
    const queue = [...items];
    /**
     * Calling this function will
     * - donwload one item after another until the queue is empty
     * - return a promise which resolves if the queue is empty and
     *   the last download started from this workerFn is finished
     */
    const workerFn = async () => {
        while (queue.length > 0){
            await downloadItem(queue.shift()); 
        }
    }
    /**
     * starts maxConcurrency of these workers concurrently
     * and saves the promises returned by them to an array
     */ 
    const workers = Array(maxConcurrency).fill(workerFn());
    /**
     * will resolve when all worker promises have resolved
     * (all downloads are done)
     * reject if one rejects
     */
    return Promise.all(workers);
}