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

无Wi-Fi环境下DownloadManager发送ACTION_DOWNLOAD_COMPLETE广播问题

问题分析与解决方案

核心问题拆解

  1. 网络切换后任务终止:日志显示任务因CANNOT_RESUME终止,本质原因是服务器未开启断点续传支持,或客户端未正确处理续传逻辑;同时代码收到ACTION_DOWNLOAD_COMPLETE广播后直接删除临时文件,彻底阻断了续传可能。
  2. 广播认知偏差:ACTION_DOWNLOAD_COMPLETE广播会在任务无论成功/失败时都触发,官方文档明确此行为,需通过查询任务状态区分处理。
  3. 主动终止任务:日志中cancel() called by app表明代码存在不必要的dm.cancel()调用,直接导致任务无法自动重试。

分步解决方案

1. 先验证服务器是否支持断点续传

DownloadManager的续传依赖服务器支持Range请求,用以下命令测试:

curl -I [你的下载URL]

检查响应头是否包含Accept-Ranges: bytes:

  • 若存在:服务器支持续传,可继续优化客户端逻辑;
  • 若不存在:需后端配置服务器开启(比如Nginx添加add_header Accept-Ranges bytes;),否则无法实现续传,只能优化重试逻辑。

2. 修改ACTION_DOWNLOAD_COMPLETE广播处理逻辑

不要一收到广播就删除临时文件,先查询任务状态,按需处理:

@Override
public void onReceive(Context context, Intent intent) {
    long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
    if (downloadId == -1) return;

    DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
    DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
    Cursor cursor = dm.query(query);
    
    if (cursor.moveToFirst()) {
        int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
        int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
        File partialFile = new File(fullFilePath); // 你的临时文件路径

        switch (status) {
            case DownloadManager.STATUS_SUCCESSFUL:
                // 处理下载完成逻辑
                break;
            case DownloadManager.STATUS_FAILED:
                switch (reason) {
                    // 无法续传(多因服务器不支持Range)
                    case DownloadManager.ERROR_CANNOT_RESUME:
                        if (partialFile.exists() && partialFile.length() > 0) {
                            // 若服务器支持Range,手动发起续传请求
                            Request resumeRequest = new Request(Uri.parse(updateInfo.getDownloadUrl()));
                            resumeRequest.addRequestHeader("Range", "bytes=" + partialFile.length() + "-");
                            // 复用原请求的其他配置
                            resumeRequest.setDestinationUri(Uri.parse(fullFilePath));
                            resumeRequest.setAllowedOverRoaming(true);
                            resumeRequest.setAllowedOverMetered(true);
                            // 重新入队续传
                            long newId = dm.enqueue(resumeRequest);
                            updateInfo.setDownloadId(newId);
                        } else {
                            // 文件已丢失,重新发起完整请求
                            reEnqueueFullDownload(context, updateInfo);
                        }
                        break;
                    // 网络切换/连接失败,等待网络恢复后自动重试,不删文件
                    case DownloadManager.ERROR_NETWORK_CHANGED:
                    case DownloadManager.ERROR_CONNECTION_FAILED:
                        break;
                    // 其他不可恢复错误(比如文件路径无效),再删除临时文件
                    default:
                        if (partialFile.exists()) partialFile.delete();
                        break;
                }
                break;
            case DownloadManager.STATUS_PAUSED:
                // 任务暂停(比如等待网络),无需处理,DownloadManager会自动重试
                break;
        }
    }
    cursor.close();
}

3. 移除不必要的cancel()调用

检查代码中所有dm.cancel(downloadId)的调用场景,仅在用户主动取消下载时执行,避免在失败回调中误触发,否则会直接终止任务的自动重试机制。

4. 监听网络状态,优化重试逻辑

针对Android 7.0+,使用ConnectivityManager.NetworkCallback监听网络切换:

  • 当网络恢复时,查询当前未完成的下载任务状态;
  • 若任务处于STATUS_PAUSED或STATUS_FAILED(非致命错误),可调用dm.enqueue(request)重新触发任务(注意复用原请求配置)。

额外优化建议

  • 去掉请求中的Cache-Control: no-cache:此头会强制服务器返回完整文件,不利于续传;
  • 给请求添加合理的重试策略:虽然DownloadManager默认会重试,但可通过setRetryAfterMillis()(API 29+)自定义重试间隔;
  • 确保下载路径的可写性:避免因权限问题导致临时文件被系统清理。

内容的提问来源于stack exchange,提问作者Top4o

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:20:27