Skip to content

legado_app_beta_3.26.081715

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Aug 07:57
· 1 commit to main since this release
57ae22c

更新内容

  • [修复 优化]:修复离线缓存界面,单本下载取消后 CacheBookService 通知栏有时无法正常关闭的 Bug;修复缓存任务列表未及时清理引起的内存泄漏,及结束时的异常取消问题

内存泄漏和协程竞态问题修复

(首先补充大量了对僵尸任务 (ZombieTask) 的日志打点,方便后续排查生命周期问题。)

a. 修复离线缓存界面,单本下载取消后 CacheBookService 通知栏有时无法正常自动关闭的 Bug

Bug 现象:
同时下载多本书籍时,如果在离线缓存界面通过点击列表单独取消书籍(暂停),当取消到最后一本时,有时通知栏无法消失,并死卡在“正在下载:0|等待中:0|失败:0|成功:X(历史累计成功数)”的状态。

根本原因:

  1. 触发单本取消 (IntentAction.remove) 时,内部调用 CacheBookModel.stop(),其通过 tasks.clear() 异步取消下载协程。
  2. 由于协程的 cancel 回调是异步执行的,此时同步代码往下走,onDownloadSet(正在下载集合)并未立即清空。
  3. 同步触发的 onFinally()onDownloadSet 不为空,拒绝将该书从 cacheBookMap 中移除。
  4. 导致外层 CacheBookServiceif (cacheBookMap.isEmpty()) 判定为 false,完美错失了调用 stopSelf() 的唯一时机。
  5. 服务变成“僵尸进程”,其内部轮询持续读取未清零的 successDownloadSet 历史数据,并不断刷新通知栏。
    (此时点击取消可以正常销毁通知)

修复逻辑:
CacheBookModel.stop() 中加入onDownloadSet.clear()。确保即使单本停止时也同步清空运行队列,让后续的 onFinally() 能立即将书籍踢出 cacheBookMap,从而顺利触发 stopSelf() 销毁服务。

b. 修复缓存任务列表未及时清理引起的内存泄漏,及结束时的异常取消问题

问题背景:
当我们在进行书籍缓存下载时,会在后台输出大量 Coroutine.cancel()onCancel() called 日志。特别是在停止缓存服务(如接收到 IntentAction.stop 停止广播)时,会瞬间对所有历史成功完成的任务再次调用取消并打印错误堆栈。这暴露出任务管理上的内存泄漏和底层协程框架的方法缺陷。

修复详情:

  1. 修复 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 产生死循环。
  2. 修复 Coroutine.kt 的取消设计缺陷:
    修改自定义协程封装 Coroutine.kt 中的 cancel() 方法。之前该方法缺少对生命周期的把控,只要被调用就会无条件触发内部的 onCancel 回调。导致原本的错误内存泄漏下,如 CompositeCoroutine.clear() 等批量清理方法误杀早已结束的无辜任务。

    • 修改方案:在 cancel() 方法头部加入 if (job.isCompleted) return 拦截。当探测到目标底层 Job 已经执行结束时,阻断取消回调,并且打印异常调用的追踪日志 (ZombieTask Log: Coroutine.Cancel() 错误调用...) 进行监控。