如何从GameController类定时更新Fragment中的TextView?
优雅实现GameController定时更新Fragment视图的方案
嘿,这个问题太常见了——直接用静态方法操作Fragment的视图确实是个不良实践,很容易引发内存泄漏、生命周期不一致的问题,还会让代码耦合度变得很高。下面给你几个Android开发中更合理的实现方式,既能满足每秒更新的需求,又能遵循组件化的最佳实践:
方案一:使用LiveData + ViewModel(推荐)
这是Jetpack组件的标准做法,能完美解耦GameController和GameActivity_InfoPane,还能自动感知Fragment的生命周期,避免内存泄漏。
步骤:
- 创建一个共享的ViewModel,持有用于存储游戏时间的LiveData:
class GameViewModel : ViewModel() { private val _gameTime = MutableLiveData<Long>(0) val gameTime: LiveData<Long> = _gameTime fun updateGameTime(newTime: Long) { _gameTime.postValue(newTime) } }
- 在
GameController中获取ViewModel实例,并定时更新LiveData的值:
class GameController { private lateinit var gameViewModel: GameViewModel // 初始化时传入ViewModel(可以从Activity或Fragment获取) fun initViewModel(viewModel: GameViewModel) { this.gameViewModel = viewModel } fun startTimer() { val timer = Timer() timer.scheduleAtFixedRate(object : TimerTask() { var currentTime = 0L override fun run() { currentTime++ gameViewModel.updateGameTime(currentTime) } }, 0, 1000) } }
- 在
GameActivity_InfoPane中观察LiveData,自动更新UI:
class GameActivity_InfoPane : Fragment() { private lateinit var gameViewModel: GameViewModel private lateinit var gameTimeCount: TextView override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_info_pane, container, false) gameTimeCount = view.findViewById(R.id.game_time_count) return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) gameViewModel = ViewModelProvider(requireActivity()).get(GameViewModel::class.java) // 观察LiveData变化,更新TextView gameViewModel.gameTime.observe(viewLifecycleOwner) { time -> gameTimeCount.text = "游戏时长:$time 秒" } } }
优点:
- 完全解耦控制器和Fragment,两者只通过ViewModel通信
- LiveData会自动在Fragment处于活跃状态时更新UI,生命周期安全
- 不会出现内存泄漏,因为ViewModel的生命周期和Activity/Fragment绑定
方案二:接口回调(Listener模式)
如果不想用Jetpack组件,接口回调是更传统的解耦方式,灵活性很高。
步骤:
- 定义一个更新UI的回调接口:
interface GameTimeUpdateListener { fun onGameTimeUpdated(time: Long) }
- 在
GameController中持有Listener的引用,并定时调用接口方法:
class GameController { // 使用弱引用避免内存泄漏 private var listener: WeakReference<GameTimeUpdateListener>? = null fun setGameTimeListener(listener: GameTimeUpdateListener) { this.listener = WeakReference(listener) } fun startTimer() { val timer = Timer() timer.scheduleAtFixedRate(object : TimerTask() { var currentTime = 0L override fun run() { currentTime++ // 在主线程更新UI Handler(Looper.getMainLooper()).post { listener?.get()?.onGameTimeUpdated(currentTime) } } }, 0, 1000) } }
- 在
GameActivity_InfoPane中实现接口,并在生命周期中注册/反注册:
class GameActivity_InfoPane : Fragment(), GameTimeUpdateListener { private lateinit var gameController: GameController private lateinit var gameTimeCount: TextView override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_info_pane, container, false) gameTimeCount = view.findViewById(R.id.game_time_count) gameController = GameController() return view } override fun onResume() { super.onResume() gameController.setGameTimeListener(this) } override fun onPause() { super.onPause() // 移除引用,避免内存泄漏 gameController.setGameTimeListener(null) } override fun onGameTimeUpdated(time: Long) { gameTimeCount.text = "游戏时长:$time 秒" } }
优点:
- 代码轻量,不需要引入额外依赖
- 耦合度低,控制器只依赖抽象接口,不直接耦合Fragment
- 可以灵活控制监听的生命周期,避免不必要的更新
方案三:事件总线(EventBus/RxJava Subject)
如果你的项目中有多组件需要通信,事件总线是个不错的选择,但要注意不要过度使用,否则会让代码难以追踪。
示例(用GreenRobot EventBus):
- 定义一个时间更新事件类:
class GameTimeUpdateEvent(val time: Long)
- 在
GameController中发送事件:
class GameController { fun startTimer() { val timer = Timer() timer.scheduleAtFixedRate(object : TimerTask() { var currentTime = 0L override fun run() { currentTime++ EventBus.getDefault().post(GameTimeUpdateEvent(currentTime)) } }, 0, 1000) } }
- 在
GameActivity_InfoPane中注册并订阅事件:
class GameActivity_InfoPane : Fragment() { private lateinit var gameTimeCount: TextView override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_info_pane, container, false) gameTimeCount = view.findViewById(R.id.game_time_count) return view } override fun onStart() { super.onStart() EventBus.getDefault().register(this) } override fun onStop() { super.onStop() EventBus.getDefault().unregister(this) } @Subscribe(threadMode = ThreadMode.MAIN) fun onGameTimeUpdate(event: GameTimeUpdateEvent) { gameTimeCount.text = "游戏时长:${event.time} 秒" } }
优点:
- 适合多组件跨层级通信
- 不需要直接持有组件引用,降低耦合
注意:
- 要记得在生命周期中注册和反注册,避免内存泄漏
- 过多使用事件总线会让代码的数据流变得难以追踪,简单场景优先用前两种方案
内容的提问来源于stack exchange,提问作者ImStupid
相关产品推荐
相关产品推荐

