You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从GameController类定时更新Fragment中的TextView?

优雅实现GameController定时更新Fragment视图的方案

嘿,这个问题太常见了——直接用静态方法操作Fragment的视图确实是个不良实践,很容易引发内存泄漏、生命周期不一致的问题,还会让代码耦合度变得很高。下面给你几个Android开发中更合理的实现方式,既能满足每秒更新的需求,又能遵循组件化的最佳实践:

方案一:使用LiveData + ViewModel(推荐)

这是Jetpack组件的标准做法,能完美解耦GameController和GameActivity_InfoPane,还能自动感知Fragment的生命周期,避免内存泄漏。

步骤:

  1. 创建一个共享的ViewModel,持有用于存储游戏时间的LiveData:
class GameViewModel : ViewModel() {
    private val _gameTime = MutableLiveData<Long>(0)
    val gameTime: LiveData<Long> = _gameTime

    fun updateGameTime(newTime: Long) {
        _gameTime.postValue(newTime)
    }
}
  1. 在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)
    }
}
  1. 在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组件,接口回调是更传统的解耦方式,灵活性很高。

步骤:

  1. 定义一个更新UI的回调接口:
interface GameTimeUpdateListener {
    fun onGameTimeUpdated(time: Long)
}
  1. 在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)
    }
}
  1. 在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):

  1. 定义一个时间更新事件类:
class GameTimeUpdateEvent(val time: Long)
  1. 在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)
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:19:55