Wear OS 磁贴和媒体服务
Wear OS Tiles and Media Service
Wear OS tiles 示例很棒,问题不大,但是当我每次尝试启动该服务时,如何启动播放在主要应用中选择的歌曲的后台媒体服务,我收到以下错误。没有 UI 线程可供参考,文档只有 onclick、LoadAction 和 LaunchAction 的方法。
override fun onTileRequest(request: TileRequest) = serviceScope.future {
when(request.state!!.lastClickableId){
"play"-> playClicked()
}....
suspend fun playClicked(){
try {
// Convert the asynchronous callback to a suspending coroutine
suspendCancellableCoroutine<Unit> { cont ->
mMediaBrowserCompat = MediaBrowserCompat(
applicationContext, ComponentName(applicationContext, MusicService::class.java),
mMediaBrowserCompatConnectionCallback, null
)
mMediaBrowserCompat!!.connect()
}
}catch (e:Exception){
e.printStackTrace()
} finally {
mMediaBrowserCompat!!.disconnect()
}
}
错误
java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-1,5,main] that has not called Looper.prepare()
serviceScope 在 Dispatchers.IO 上 运行,您应该在调用 MediaBrowserCompat 时使用 withContext(Dispatchers.Main)。
感谢 Yuri 的工作,但它最终阻塞了 UI 线程,有效的解决方案如下
fun playClicked(){
mainHandler.post(playSong)
}
private val playSong: Runnable = object : Runnable {
@RequiresApi(Build.VERSION_CODES.N)
override fun run() {
mMediaBrowserCompat = MediaBrowserCompat(
applicationContext, ComponentName(applicationContext, MusicaWearService::class.java),
mMediaBrowserCompatConnectionCallback, null
)
mMediaBrowserCompat!!.connect()
}
}```
响应上面的答案,serviceScope.future 创建了一个 CoroutineScope,它将导致未来 returned 到服务等待所有子作业完成。
如果你想让它 运行 从 onTileRequest 调用中分离出来,你可以 运行 以下,这将在应用程序 GlobalScope 中启动一个新作业并让 onTileRequest return 立即。
"play"-> GlobalScope.launch {
}
这样做的好处是您不会将第三个并发模型混入其中,ListenableFutures、Coroutines 和现在的 Handler。 LF 和协程旨在避免您不得不求助于第三种并发选项。
酷 Yuri,下面的方法有效,我认为效率更高
fun playClicked() = GlobalScope.launch(Dispatchers.Main) {
mMediaBrowserCompat = MediaBrowserCompat(
applicationContext, ComponentName(applicationContext, MusicaWearService::class.java),
mMediaBrowserCompatConnectionCallback, null
)
mMediaBrowserCompat!!.connect()
}
Wear OS tiles 示例很棒,问题不大,但是当我每次尝试启动该服务时,如何启动播放在主要应用中选择的歌曲的后台媒体服务,我收到以下错误。没有 UI 线程可供参考,文档只有 onclick、LoadAction 和 LaunchAction 的方法。
override fun onTileRequest(request: TileRequest) = serviceScope.future {
when(request.state!!.lastClickableId){
"play"-> playClicked()
}....
suspend fun playClicked(){
try {
// Convert the asynchronous callback to a suspending coroutine
suspendCancellableCoroutine<Unit> { cont ->
mMediaBrowserCompat = MediaBrowserCompat(
applicationContext, ComponentName(applicationContext, MusicService::class.java),
mMediaBrowserCompatConnectionCallback, null
)
mMediaBrowserCompat!!.connect()
}
}catch (e:Exception){
e.printStackTrace()
} finally {
mMediaBrowserCompat!!.disconnect()
}
}
错误
java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-1,5,main] that has not called Looper.prepare()
serviceScope 在 Dispatchers.IO 上 运行,您应该在调用 MediaBrowserCompat 时使用 withContext(Dispatchers.Main)。
感谢 Yuri 的工作,但它最终阻塞了 UI 线程,有效的解决方案如下
fun playClicked(){
mainHandler.post(playSong)
}
private val playSong: Runnable = object : Runnable {
@RequiresApi(Build.VERSION_CODES.N)
override fun run() {
mMediaBrowserCompat = MediaBrowserCompat(
applicationContext, ComponentName(applicationContext, MusicaWearService::class.java),
mMediaBrowserCompatConnectionCallback, null
)
mMediaBrowserCompat!!.connect()
}
}```
响应上面的答案,serviceScope.future 创建了一个 CoroutineScope,它将导致未来 returned 到服务等待所有子作业完成。
如果你想让它 运行 从 onTileRequest 调用中分离出来,你可以 运行 以下,这将在应用程序 GlobalScope 中启动一个新作业并让 onTileRequest return 立即。
"play"-> GlobalScope.launch {
}
这样做的好处是您不会将第三个并发模型混入其中,ListenableFutures、Coroutines 和现在的 Handler。 LF 和协程旨在避免您不得不求助于第三种并发选项。
酷 Yuri,下面的方法有效,我认为效率更高
fun playClicked() = GlobalScope.launch(Dispatchers.Main) {
mMediaBrowserCompat = MediaBrowserCompat(
applicationContext, ComponentName(applicationContext, MusicaWearService::class.java),
mMediaBrowserCompatConnectionCallback, null
)
mMediaBrowserCompat!!.connect()
}