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()
        }