前段时间,使用流利说的 FileDownloader,来实现视频下载器 app 的多任务下载的功能。在开发过程中,查看了不少官方的 issue,觉得有必要记录一下,希望能够加快大家集成的进度。
导入方式,初始化的知识可以查看官方的文档。官方文档有中文版的,可以多看几遍,发现有用的 API。这里记录一下几个解决的问题:
可以查看 https://github.com/lingochamp/FileDownloader/issues/4,作者给出了一个解决办法:思路是通过第三方DownloadManager将FileDownloadTask从界面中解耦出来。
当然,如果项目中使用了 EventBus 的话,可以使用 EventBus 把下载回调信息发出去,在其他页面在通过设置 EventBus 的接收事件方法就可以了。
可以查看 https://github.com/lingochamp/FileDownloader/issues/932。
作者给出了详细的解释:progress 的回调由回调时间间隔和回调次数共同控制。对应的 API 是 BaseDownloadTask#setCallbackProgressMinInterval(int) 和 BaseDownloadTask#setCallbackProgressTimes(int)。
比如,设置最大线程数为 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.
可以查看 https://github.com/lingochamp/FileDownloader/issues/1139。
实际中发现了,已经暂停的任务,又显示在了界面上。解决办法:就是在这个删除的任务回调时,判断它的状态若等于暂停,就不再走下面的逻辑。
这个功能,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。
当然,自己实现也是可以的。需要注意的地方是,把创建好的通知缓存起来,避免下次更新时重新创建通知,造成通知栏闪动。
可以查看默认的生成下载 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。应该说,很少会重复的。
这一条是最重要的。因为出现了问题,FileDownloader 的日志是很详细的,甚至会给出建议的解决办法。
在自己的 Application 中,设置 FileDownloadLog.NEED_LOG=true,这样就打开日志;
查看日志时,过滤 FileDownloader.,就可以了。
需要注意的是,不仅要查看主进程的日志,还要查看 :filedownloader 进程的日志。
对于日志,要仔细查看。另外,反馈 issue,日志也是越详细越好,作者才能更好地解决我们的问题。
FileDownloader 这个优秀的开源项目,确实给业务带来了方便。但是,它也很复杂,需要不断地学习,才能更好地在项目中发挥作用。其实,项目中还有未解决的问题:比如,bilibili 上的视频连接下载不了。但是,竞品确可以下载。有解决的同学,帮忙在留言区讨论一下。