如何在另一个Fragment中停止CountDownTimer
核心原则:不要在C Fragment中直接持有A Fragment的实例去调用停止方法,这种强耦合写法极易触发空指针、内存泄漏问题,以下是经过生产环境验证的方案:
方案1:宿主Activity持有计时器实例(优先选,实现简单无坑)
不要把CountDownTimer定义在A Fragment内部,把实例的初始化、启动、停止逻辑全部收敛到三个Fragment共同的宿主Activity中:- Activity中定义计时器实例和对应的启动、停止公开方法,同时在Activity销毁时主动取消计时器,避免泄漏
- A Fragment中通过
requireActivity()拿到宿主实例,调用公开的启动方法触发计时 - C Fragment中同样拿到宿主实例,调用公开的停止方法即可终止计时
核心代码参考:
// 宿主Activity代码示例 public class ContainerActivity extends AppCompatActivity { private CountDownTimer mTimer; public void startCountDown(long totalMills, long intervalMills) { // 启动前先取消已有计时器,避免重复创建 if (mTimer != null) mTimer.cancel(); mTimer = new CountDownTimer(totalMills, intervalMills) { @Override public void onTick(long millisUntilFinished) { // 可通过LiveData、接口回调把剩余时间分发给需要更新UI的页面 } @Override public void onFinish() { // 计时结束逻辑 } }.start(); } public void stopCountDown() { if (mTimer != null) { mTimer.cancel(); mTimer = null; } } @Override protected void onDestroy() { super.onDestroy(); stopCountDown(); } }A Fragment启动计时:
((ContainerActivity)requireActivity()).startCountDown(60000, 1000);C Fragment停止计时:
((ContainerActivity)requireActivity()).stopCountDown();方案2:Activity级共享ViewModel持有计时器(适合MVVM架构项目)
如果项目用了MVVM架构,可以给A、B、C三个Fragment绑定作用域为宿主Activity的共享ViewModel,把CountDownTimer的逻辑放在ViewModel中实现:- ViewModel中实现计时器的启动、停止逻辑,在
onCleared()回调中主动取消计时器 - A Fragment通过
activityViewModels()拿到共享ViewModel实例,调用启动方法 - C Fragment拿到同一个ViewModel实例,调用停止方法即可
注意:不要给每个Fragment单独绑定自己作用域的ViewModel,否则不同Fragment拿到的不是同一个实例,无法控制同一个计时器
- ViewModel中实现计时器的启动、停止逻辑,在
方案3:生命周期感知的事件通知(仅适合复杂页面层级场景,不推荐小项目用)
如果页面嵌套层级深不想耦合宿主Activity,可以用本地广播、生命周期感知的事件总线实现通知:C Fragment触发停止计时的事件,A Fragment在生命周期内监听该事件,收到后取消自己持有的计时器。
该方案存在明显缺陷:如果跳转过程中A Fragment被系统回收、退栈销毁,事件会无人消费,计时器会在后台继续运行触发内存泄漏,必须做好事件的注册/解绑兜底。
绝对不要踩的坑:不要通过
findFragmentByTag等方式直接从FragmentManager中取A Fragment的实例,强行调用A内部的计时器方法。这种写法会让C和A产生强依赖,后续修改跳转链路、页面重建时大概率会出现空指针异常。
内容的提问来源于stack exchange,提问作者Isay Dehghany

