legado_app_beta_3.26.081715
Pre-release更新内容
- [修复 优化]:修复离线缓存界面,单本下载取消后 CacheBookService 通知栏有时无法正常关闭的 Bug;修复缓存任务列表未及时清理引起的内存泄漏,及结束时的异常取消问题
内存泄漏和协程竞态问题修复
(首先补充大量了对僵尸任务 (ZombieTask) 的日志打点,方便后续排查生命周期问题。)
a. 修复离线缓存界面,单本下载取消后 CacheBookService 通知栏有时无法正常自动关闭的 Bug
Bug 现象:
同时下载多本书籍时,如果在离线缓存界面通过点击列表单独取消书籍(暂停),当取消到最后一本时,有时通知栏无法消失,并死卡在“正在下载:0|等待中:0|失败:0|成功:X(历史累计成功数)”的状态。
根本原因:
- 触发单本取消 (
IntentAction.remove) 时,内部调用CacheBookModel.stop(),其通过tasks.clear()异步取消下载协程。 - 由于协程的 cancel 回调是异步执行的,此时同步代码往下走,
onDownloadSet(正在下载集合)并未立即清空。 - 同步触发的
onFinally()因onDownloadSet不为空,拒绝将该书从cacheBookMap中移除。 - 导致外层
CacheBookService的if (cacheBookMap.isEmpty())判定为 false,完美错失了调用stopSelf()的唯一时机。 - 服务变成“僵尸进程”,其内部轮询持续读取未清零的
successDownloadSet历史数据,并不断刷新通知栏。
(此时点击取消可以正常销毁通知)
修复逻辑:
CacheBookModel.stop() 中加入onDownloadSet.clear()。确保即使单本停止时也同步清空运行队列,让后续的 onFinally() 能立即将书籍踢出 cacheBookMap,从而顺利触发 stopSelf() 销毁服务。
b. 修复缓存任务列表未及时清理引起的内存泄漏,及结束时的异常取消问题
问题背景:
当我们在进行书籍缓存下载时,会在后台输出大量 Coroutine.cancel() 和 onCancel() called 日志。特别是在停止缓存服务(如接收到 IntentAction.stop 停止广播)时,会瞬间对所有历史成功完成的任务再次调用取消并打印错误堆栈。这暴露出任务管理上的内存泄漏和底层协程框架的方法缺陷。
修复详情:
-
修复
CacheBook.kt中的内存泄漏:
在CacheBookModel中启动章节下载时,每个下载协程都会通过tasks.add被加入CompositeCoroutine中保存。但任务自然结束(成功/失败)后,缺少移除逻辑,导致tasks随下载章节无限膨胀。- 修改方案:使用
.also { coroutine -> ... }取代原有的.apply { ... }和.let { ... }链式调用。在确保明确的coroutine变量引用的前提下,在协程自带的.onFinally { ... }生命周期回调中加入tasks.remove(coroutine)。保证每一个任务在执行生命周期终了时都能将自己移出缓存管理队列。 - 采用
tasks.delete(coroutine)而不是tasks.remove(it),避免因remove内部的联动cancel()触发了onCancel回调,从而把该章节错误地塞回waitDownloadSet产生死循环。
- 修改方案:使用
-
修复
Coroutine.kt的取消设计缺陷:
修改自定义协程封装Coroutine.kt中的cancel()方法。之前该方法缺少对生命周期的把控,只要被调用就会无条件触发内部的onCancel回调。导致原本的错误内存泄漏下,如CompositeCoroutine.clear()等批量清理方法误杀早已结束的无辜任务。- 修改方案:在
cancel()方法头部加入if (job.isCompleted) return拦截。当探测到目标底层Job已经执行结束时,阻断取消回调,并且打印异常调用的追踪日志 (ZombieTask Log: Coroutine.Cancel() 错误调用...) 进行监控。
- 修改方案:在