如何修复此警告“GlobalScope,这是一个微妙的 API,其使用需要小心”?

问题描述 投票:0回答:1

我目前正在学习如何使用 compose 和 media3 构建音乐应用程序。 onIsPlayingChanged(isPlaying: Boolean) 在这个方法中我使用 GlobalScope,Android studio 给我这个警告“这是一个微妙的 API,它的使用需要小心。确保你完全阅读并理解标记为微妙的声明的文档API。”

我尝试使用另一个作业变量,但我不知道该怎么做。如何修复代码以遵循 Google android 开发最佳实践。 这是 MusicServiceHandler 的代码

import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.techullurgy.media3musicplayer.utils.MediaStateEvents
import com.techullurgy.media3musicplayer.utils.MusicStates
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class MusicServiceHandler(
private val exoPlayer: Player,
) : Player.Listener {

private val _musicStates: MutableStateFlow<MusicStates> = MutableStateFlow(MusicStates.Initial)
val musicStates: StateFlow<MusicStates> = _musicStates.asStateFlow()

private var job: Job? = null

init {
    exoPlayer.addListener(this)
}

fun setMediaItem(mediaItem: MediaItem) {
    exoPlayer.setMediaItem(mediaItem)
    exoPlayer.prepare()
}

fun setMediaItemList(mediaItems: List<MediaItem>) {
    exoPlayer.setMediaItems(mediaItems)
    exoPlayer.prepare()
}

suspend fun onMediaStateEvents(
    mediaStateEvents: MediaStateEvents,
    selectedMusicIndex: Int = -1,
    seekPosition: Long = 0,
) {
    when (mediaStateEvents) {
        MediaStateEvents.Backward -> exoPlayer.seekBack()
        MediaStateEvents.Forward -> exoPlayer.seekForward()
        MediaStateEvents.PlayPause -> playPauseMusic()
        MediaStateEvents.SeekTo -> exoPlayer.seekTo(seekPosition)
        MediaStateEvents.SeekToNext -> exoPlayer.seekToNext()
        MediaStateEvents.SeekToPrevious -> exoPlayer.seekToPrevious()
        MediaStateEvents.Stop -> stopProgressUpdate()
        MediaStateEvents.SelectedMusicChange -> {
            when (selectedMusicIndex) {
                exoPlayer.currentMediaItemIndex -> {
                    playPauseMusic()
                }

                else -> {
                    exoPlayer.seekToDefaultPosition(selectedMusicIndex)
                    _musicStates.value = MusicStates.MediaPlaying(
                        isPlaying = true
                    )
                    exoPlayer.playWhenReady = true
                    startProgressUpdate()
                }
            }
        }

        is MediaStateEvents.MediaProgress -> {
            exoPlayer.seekTo(
                (exoPlayer.duration * mediaStateEvents.progress).toLong()
            )
        }
    }
}

override fun onPlaybackStateChanged(playbackState: Int) {
    when (playbackState) {
        ExoPlayer.STATE_BUFFERING -> _musicStates.value =
            MusicStates.MediaBuffering(exoPlayer.currentPosition)

        ExoPlayer.STATE_READY -> _musicStates.value = MusicStates.MediaReady(exoPlayer.duration)

        Player.STATE_ENDED -> {
            // no-op
        }

        Player.STATE_IDLE -> {
            // no-op
        }
    }
}

@OptIn(DelicateCoroutinesApi::class)
override fun onIsPlayingChanged(isPlaying: Boolean) {
    _musicStates.value = MusicStates.MediaPlaying(isPlaying = isPlaying)
    _musicStates.value = MusicStates.CurrentMediaPlaying(exoPlayer.currentMediaItemIndex)
    if (isPlaying) {
        GlobalScope.launch(Dispatchers.Main) {
            startProgressUpdate()
        }
    } else {
        stopProgressUpdate()
    }
}

private suspend fun playPauseMusic() {
    if (exoPlayer.isPlaying) {
        exoPlayer.pause()
        stopProgressUpdate()
    } else {
        exoPlayer.play()
        _musicStates.value = MusicStates.MediaPlaying(
            isPlaying = true
        )
        startProgressUpdate()
    }
}

private suspend fun startProgressUpdate() = job.run {
    while (true) {
        delay(500)
        _musicStates.value = MusicStates.MediaProgress(exoPlayer.currentPosition)
    }
}

private fun stopProgressUpdate() {
    job?.cancel()
    _musicStates.value = MusicStates.MediaPlaying(isPlaying = false)
}
}

这个 MusicViewModel

@OptIn(SavedStateHandleSaveableApi::class)
class MusicViewModel(
savedStateHandle: SavedStateHandle,
) : ViewModel(), KoinComponent {

private val musicServiceHandler: MusicServiceHandler by inject<MusicServiceHandler>()
private val repository: MusicRepository by inject<MusicRepository>()

private var duration by savedStateHandle.saveable { mutableLongStateOf(0L) }
var progress by savedStateHandle.saveable { mutableFloatStateOf(0f) }
private var progressValue by savedStateHandle.saveable { mutableStateOf("00:00") }
var isMusicPlaying by savedStateHandle.saveable { mutableStateOf(false) }
var currentSelectedMusic by mutableStateOf(
        AudioItem(
            0L,
            "".toUri(),
            "",
            "",
            0,
            "",
            "",
            null
        )
    )

var musicList by mutableStateOf(listOf<AudioItem>())

private val _homeUiState: MutableStateFlow<HomeUIState> =
    MutableStateFlow(HomeUIState.InitialHome)
val homeUIState: StateFlow<HomeUIState> = _homeUiState.asStateFlow()

init {
    getMusicData()
}

init {
    viewModelScope.launch {
        musicServiceHandler.musicStates.collectLatest { musicStates: MusicStates ->
            when (musicStates) {
                MusicStates.Initial -> _homeUiState.value = HomeUIState.InitialHome
                is MusicStates.MediaBuffering -> progressCalculation(musicStates.progress)
                is MusicStates.MediaPlaying -> isMusicPlaying = musicStates.isPlaying
                is MusicStates.MediaProgress -> progressCalculation(musicStates.progress)
                is MusicStates.CurrentMediaPlaying -> {
                    currentSelectedMusic = musicList[musicStates.mediaItemIndex]
                }

                is MusicStates.MediaReady -> {
                    duration = musicStates.duration
                    _homeUiState.value = HomeUIState.HomeReady
                }
            }
        }
    }
}

fun onHomeUiEvents(homeUiEvents: HomeUiEvents) = viewModelScope.launch {
    when (homeUiEvents) {
        HomeUiEvents.Backward -> musicServiceHandler.onMediaStateEvents(MediaStateEvents.Backward)
        HomeUiEvents.Forward -> musicServiceHandler.onMediaStateEvents(MediaStateEvents.Forward)
        HomeUiEvents.SeekToNext -> musicServiceHandler.onMediaStateEvents(MediaStateEvents.SeekToNext)
        HomeUiEvents.SeekToPrevious -> musicServiceHandler.onMediaStateEvents(MediaStateEvents.SeekToPrevious)
        is HomeUiEvents.PlayPause -> {
            musicServiceHandler.onMediaStateEvents(
                MediaStateEvents.PlayPause
            )
        }

        is HomeUiEvents.SeekTo -> {
            musicServiceHandler.onMediaStateEvents(
                MediaStateEvents.SeekTo,
                seekPosition = ((duration * homeUiEvents.position) / 100f).toLong()
            )
        }

        is HomeUiEvents.CurrentAudioChanged -> {
            musicServiceHandler.onMediaStateEvents(
                MediaStateEvents.SelectedMusicChange,
                selectedMusicIndex = homeUiEvents.index
            )
        }

        is HomeUiEvents.UpdateProgress -> {
            musicServiceHandler.onMediaStateEvents(
                MediaStateEvents.MediaProgress(
                    homeUiEvents.progress
                )
            )
            progress = homeUiEvents.progress
        }

    }
}

private fun getMusicData() {
    viewModelScope.launch {
        val musicData = repository.getAudioData()
        musicList = musicData
        setMusicItems()
    }
}

private fun setMusicItems() {
    musicList.map { audioItem ->
        MediaItem.Builder()
            .setUri(audioItem.uri)
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setAlbumArtist(audioItem.artist)
                    .setDisplayTitle(audioItem.title)
                    .setSubtitle(audioItem.displayName)
                    .build()
            )
            .build()
    }.also {
        musicServiceHandler.setMediaItemList(it)
    }
}

private fun progressCalculation(currentProgress: Long) {
    progress =
        if (currentProgress > 0) ((currentProgress.toFloat() / duration.toFloat()) * 100f)
        else 0f

    progressValue = formatDurationValue(currentProgress)
}

private fun formatDurationValue(duration: Long): String {
    val minutes = MINUTES.convert(duration, MILLISECONDS)
    val seconds = (minutes) - minutes * SECONDS.convert(1, MINUTES)

    return String.format("%02d:%02d", minutes, seconds)
}

override fun onCleared() {
    viewModelScope.launch {
        musicServiceHandler.onMediaStateEvents(MediaStateEvents.Stop)
    }
    super.onCleared()
}

}

android kotlin android-jetpack-compose kotlin-coroutines android-media3
1个回答
0
投票

您可以通过一些小的更改来删除全局范围的使用。让您的

MusicHandler
班级实现
CoroutineScope

class MusicServiceHandler(private val exoPlayer: Player) : Player.Listener, CoroutineScope

然后您需要覆盖

coroutineContext
变量

private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
    get() = Dispatchers.Main.immediate + job

然后将您的 GlobalScope 使用更改为仅启动

launch() {
    startProgressUpdate()
}

按如下方式更新您的

startProgressUpdate

private suspend fun startProgressUpdate() {
    while (true) {
        delay(500)
        _musicStates.value = MusicStates.MediaProgress(exoPlayer.currentPosition)
    }
}

如果你想进一步改进你的代码,你应该从

while(true)
中删除
startProgressUpdate
(有 while(true) 循环从来都不是好事)

private suspend fun startProgressUpdate() {
    delay(500)
    _musicStates.value = MusicStates.MediaProgress(exoPlayer.currentPosition)
}

并将其移至发布中

launch() {
    while(isActive){
        startProgressUpdate()
    }
}

这样,当您取消作业时,while 循环也会取消,因为作用域未处于活动状态

© www.soinside.com 2019 - 2024. All rights reserved.