处理碎片中协程的正确方法

时间:2020-04-15 12:57:44

标签: android android-fragments kotlin coroutine kotlin-coroutines

以下暂停功能将在1秒延迟内更新进度条和2个TextView。进度条分别指示MP3和TextView的进度,经过时间和剩余时间。

用户可以离开片段,然后再次返回片段,这意味着片段(视图)被破坏并再次创建。

我想知道这种实现是否正确和/或是否有更好的实现和/或替代方法(有史以来第一次实现协程)。这是一些代码:

class BookViewFragment : Fragment(), CoroutineScope {
    private var _binding: FragmentBookViewerBinding? = null
    private val bookViewFragmentBinding get() = _binding!!

    private lateinit var job: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentBookViewerBinding.inflate(layoutInflater)
        val view = bookViewFragmentBinding.root
        job = Job()
        initMediaPlayer()
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        job.cancel()
        _binding = null
        mp.stop()
        mp.release()
    }

    private fun initMediaPlayer() {
        mp = MediaPlayer()

        mp.run {
            setDataSource(...)
            setVolume(0.5f, 0.5f)
            prepare()
        }
        totalTime = mp.duration
        initPositionBar()
    }

    private fun initPositionBar() {
        bookViewFragmentBinding.mediaPosition.max = totalTime

        launch {
            setTimeOnProgressBar()
        }
    }

    private suspend fun setTimeOnProgressBar() {
        coroutineScope {
            launch {
                var progress = mp.currentPosition
                while (progress < mp.duration) {
                    progress = mp.currentPosition
                    bookViewFragmentBinding.mediaPosition.progress = progress
                    val timePlayed = progress
                    val timeLeft = mp.duration - timePlayed
                    bookViewFragmentBinding.timePlayed.text = formatIntToTime(timePlayed)
                    bookViewFragmentBinding.timeLeft.text =
                        getString(R.string.time_left, formatIntToTime(timeLeft))
                    delay(1000)
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:1)

这看起来是正确的,但是您在循环周围创建了两个不必要的协程层。在setTimeOnProgressBar()中,您已将协程包裹在一个新的coroutineScope中,而您无需使用它。可以删除它,那么它根本不必是暂停函数。因此,您还可以删除将setTimeOnProgressBar()中的调用包装到initPositionBar()的协程。

此外,您还重新创建了Android ktx库已经提供的一堆样板。您已经可以使用lifecycleScope扩展属性来启动协程,并且在onDestroyView()中会自动取消它。因此,您无需创建父作业或覆盖coroutineContext或取消父作业。

您可以在启动协同程序时使用lifecycleScope.launch

class BookViewFragment : Fragment() {
    private var _binding: FragmentBookViewerBinding? = null
    private val bookViewFragmentBinding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentBookViewerBinding.inflate(layoutInflater)
        val view = bookViewFragmentBinding.root
        initMediaPlayer()
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        mp.stop()
        mp.release()
    }

    private fun initMediaPlayer() {
        mp = MediaPlayer()

        mp.run {
            setDataSource(...)
            setVolume(0.5f, 0.5f)
            prepare()
        }
        totalTime = mp.duration
        initPositionBar()
    }

    private fun initPositionBar() {
        bookViewFragmentBinding.mediaPosition.max = totalTime

        setTimeOnProgressBar()
    }

    private fun setTimeOnProgressBar() {
        lifecycleScope.launch {
            var progress = mp.currentPosition
            while (progress < mp.duration) {
                progress = mp.currentPosition
                bookViewFragmentBinding.mediaPosition.progress = progress
                val timePlayed = progress
                val timeLeft = mp.duration - timePlayed
                bookViewFragmentBinding.timePlayed.text = formatIntToTime(timePlayed)
                bookViewFragmentBinding.timeLeft.text =
                        getString(R.string.time_left, formatIntToTime(timeLeft))
                delay(1000)
            }
        }
    }
}