集成 FileDownloader 总结

茅才
2023-12-01

1 前言

前段时间,使用流利说的 FileDownloader,来实现视频下载器 app 的多任务下载的功能。在开发过程中,查看了不少官方的 issue,觉得有必要记录一下,希望能够加快大家集成的进度。

2 正文

导入方式,初始化的知识可以查看官方的文档。官方文档有中文版的,可以多看几遍,发现有用的 API。这里记录一下几个解决的问题:

2.1 业务中需要在多个地方监听下载,怎么办?

可以查看 https://github.com/lingochamp/FileDownloader/issues/4,作者给出了一个解决办法:思路是通过第三方DownloadManager将FileDownloadTask从界面中解耦出来。
当然,如果项目中使用了 EventBus 的话,可以使用 EventBus 把下载回调信息发出去,在其他页面在通过设置 EventBus 的接收事件方法就可以了。

2.2 在列表中监听下载的进度,进度回调过于频繁,导致界面刷新太多,怎么办?

可以查看 https://github.com/lingochamp/FileDownloader/issues/932。
作者给出了详细的解释:progress 的回调由回调时间间隔和回调次数共同控制。对应的 API 是 BaseDownloadTask#setCallbackProgressMinInterval(int) 和 BaseDownloadTask#setCallbackProgressTimes(int)。

2.3 项目中可以设置最大线程数,可以调整这个值后,怎么没有效果?

比如,设置最大线程数为 3,现在开启 5 个任务,那么应该有 2 个处于等待状态。但实际上却不是。
可以查看 https://github.com/lingochamp/FileDownloader/issues/677。
作者的解释是:FileDownloader.getImpl().setMaxNetworkThreadCount(1)需要在 当前运行队列为空并且下载服务已经启动并绑定的情况下。
可以看到需要满足两个条件:1,当前运行队列为空;2,下载服务已经启动并绑定。
那么怎么办呢?
可以查看 https://github.com/lingochamp/FileDownloader/issues/320。
代码如下:

FileDownloader fileDownloader = FileDownloader.getImpl();
        fileDownloader.bindService(new Runnable() {
            @Override
            public void run() {
                LogUtils.dTag(TAG, "bindService run: currThread=" + Thread.currentThread().getName());
                // https://github.com/lingochamp/FileDownloader/issues/677
                fileDownloader.setMaxNetworkThreadCount(maxNetworkThreadCount); // 运行时设置最大并行下载的数目(网络下载线程数), [1,12]
           }
        });

查看文档,bindService 的作用是 Start and bind the FileDownloader service and run {@code runnable} as soon as the binding is successful.

2.4 已经暂停的任务,为什么还继续回调?

可以查看 https://github.com/lingochamp/FileDownloader/issues/1139。
实际中发现了,已经暂停的任务,又显示在了界面上。解决办法:就是在这个删除的任务回调时,判断它的状态若等于暂停,就不再走下面的逻辑。

2.5 想要把下载回调信息,展示在通知栏中,怎么办?

这个功能,FileDownloader 已经帮我们弄好了,可以查看 https://github.com/lingochamp/FileDownloader/wiki/FileDownloadNotificationHelper。
不过,有一点需要注意:下载大文件时,即文件大小超出了 int 的范围时,需要修改一下对应的类:FileDownloadNotificationHelper,就是把回调中的 int 值改为 long 值。可以参考下面的代码:

/**
 * The helper for notifications with downloading large tasks. You also can think this is the notifications
 * manager.
 * 这个类是由 {@link FileDownloadNotificationHelper} 修改来的,为了支持大文件下载。
 *
 * @author wzc
 * @date 2019/1/27
 * @see BaseNotificationLargeItem
 * @see FileDownloadNotificationLargeListener
 */
@SuppressWarnings("WeakerAccess")
public class FileDownloadNotificationLargeHelper<T extends BaseNotificationLargeItem> {

    private final SparseArray<T> notificationArray = new SparseArray<>();

    /**
     * Get {@link BaseNotificationLargeItem} by the download id.
     *
     * @param id The download id.
     */
    public T get(final int id) {
        return notificationArray.get(id);
    }

    public boolean contains(final int id) {
        return get(id) != null;
    }

    /**
     * Remove the {@link BaseNotificationLargeItem} by the download id.
     *
     * @param id The download id.
     * @return The removed {@link BaseNotificationLargeItem}.
     */
    public T remove(final int id) {
        final T n = get(id);
        if (n != null) {
            notificationArray.remove(id);
            return n;
        }

        return null;
    }

    /**
     * Input a {@link BaseNotificationLargeItem}.
     */
    public void add(T notification) {
        notificationArray.remove(notification.getId());
        notificationArray.put(notification.getId(), notification);
    }

    /**
     * Show the notification with the exact progress.
     *
     * @param id    The download id.
     * @param sofar The downloaded bytes so far.
     * @param total The total bytes of this task.
     */
    public void showProgress(final int id, final long sofar, final long total) {
        final T notification = get(id);

        if (notification == null) {
            return;
        }

        notification.updateStatus(FileDownloadStatus.progress);
        notification.update(sofar, total);
    }

    /**
     * Show the notification with indeterminate progress.
     *
     * @param id     The download id.
     * @param status {@link FileDownloadStatus}
     */
    public void showIndeterminate(final int id, int status) {
        final BaseNotificationLargeItem notification = get(id);

        if (notification == null) {
            return;
        }

        notification.updateStatus(status);
        notification.show(false);
    }

    /**
     * Cancel the notification by notification id.
     *
     * @param id The download id.
     */
    public void cancel(final int id) {
        final BaseNotificationLargeItem notification = remove(id);

        if (notification == null) {
            return;
        }

        notification.cancel();
    }

    /**
     * Clear and cancel all notifications which inside this helper {@link #notificationArray}.
     */
    public void clear() {
        @SuppressWarnings("unchecked") SparseArray<BaseNotificationLargeItem> cloneArray =
                (SparseArray<BaseNotificationLargeItem>) notificationArray.clone();
        notificationArray.clear();

        for (int i = 0; i < cloneArray.size(); i++) {
            final BaseNotificationLargeItem n = cloneArray.get(cloneArray.keyAt(i));
            n.cancel();
        }

    }
}

具体解释可以参考 https://github.com/lingochamp/FileDownloader/issues/673。
当然,自己实现也是可以的。需要注意的地方是,把创建好的通知缓存起来,避免下次更新时重新创建通知,造成通知栏闪动。

2.6 同一个下载 url,多次下载,对应的下载 id 还一样吗?

可以查看默认的生成下载 id 的类:

/**
 * The default id generator.
 */

public class DefaultIdGenerator implements FileDownloadHelper.IdGenerator {

    @Override
    public int transOldId(int oldId, String url, String path, boolean pathAsDirectory) {
        return generateId(url, path, pathAsDirectory);
    }

    @Override
    public int generateId(String url, String path, boolean pathAsDirectory) {
        if (pathAsDirectory) {
            return FileDownloadUtils.md5(formatString("%sp%s@dir", url, path)).hashCode();
        } else {
            return FileDownloadUtils.md5(formatString("%sp%s", url, path)).hashCode();
        }
    }
}

可以看到生成下载 id 的参数是 url 和 下载路径。经过一次 md5 哈希算法,再取出 hashCode,作为下载 id。应该说,很少会重复的。

2.7 怎样查看 FileDownloader 的日志呢?

这一条是最重要的。因为出现了问题,FileDownloader 的日志是很详细的,甚至会给出建议的解决办法。
在自己的 Application 中,设置 FileDownloadLog.NEED_LOG=true,这样就打开日志;
查看日志时,过滤 FileDownloader.,就可以了。
需要注意的是,不仅要查看主进程的日志,还要查看 :filedownloader 进程的日志。
对于日志,要仔细查看。另外,反馈 issue,日志也是越详细越好,作者才能更好地解决我们的问题。

3 最后

FileDownloader 这个优秀的开源项目,确实给业务带来了方便。但是,它也很复杂,需要不断地学习,才能更好地在项目中发挥作用。其实,项目中还有未解决的问题:比如,bilibili 上的视频连接下载不了。但是,竞品确可以下载。有解决的同学,帮忙在留言区讨论一下。

 类似资料: