Правильный способ обработки Coroutine внутри фрагмента - PullRequest
0 голосов
/ 15 апреля 2020

Следующая функция приостановки обновляет индикатор выполнения и 2 TextViews с задержкой в ​​1 секунду. Индикатор выполнения показывает прогресс 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 Ответ

1 голос
/ 15 апреля 2020

Это выглядит правильно, но вы создали два ненужных слоя сопрограммы вокруг вашего l oop. В 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)
            }
        }
    }
}
...