无Wi-Fi环境下DownloadManager发送ACTION_DOWNLOAD_COMPLETE广播问题
问题分析与解决方案
核心问题拆解
- 网络切换后任务终止:日志显示任务因
CANNOT_RESUME终止,本质原因是服务器未开启断点续传支持,或客户端未正确处理续传逻辑;同时代码收到ACTION_DOWNLOAD_COMPLETE广播后直接删除临时文件,彻底阻断了续传可能。 - 广播认知偏差:
ACTION_DOWNLOAD_COMPLETE广播会在任务无论成功/失败时都触发,官方文档明确此行为,需通过查询任务状态区分处理。 - 主动终止任务:日志中
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
相关产品推荐
相关产品推荐

