通过取消绑定所有有界活动来停止服务

Stop a service by unbinding all bounded activities

我有一个启动和绑定的服务(音乐播放器),我想在点击通知的关闭按钮时停止它。但是,如果有活动绑定到该服务,则该服务不会停止。 单击按钮时如何停止服务?

我尝试收集服务中的活动列表并调用它们的回调以解除绑定(实际上 finish() 然后在 onStop() 将调用 unbind())然后我停止该服务通过调用 stopSelf() 但我认为这不是一个好主意,因为生命周期问题和一些活动被多次添加并且维护列表很困难。应该有更好的方法!虽然我搜索了几个小时还没有找到任何东西。

这是我的 PlayerServiceonStartCommand()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    info("Starting Player Service ...")
    started=true

    if (intent == null) {
        error("intent was null")
        pause()
        return START_STICKY
    }

    if (intent.hasExtra(KEY_SONGS)) {
        if(intent.hasExtra(KEY_PLAYLIST_ID)) playlistId=intent.getStringExtra(KEY_PLAYLIST_ID)
        initExoPlayer(intent)
    } else {
        info("No new song information found for intent, intent's action: ${intent.action}")
    }
    if (intent.action == null) intent.action = ""
    when (intent.action) {
        ACTION_PLAY -> if (isPlaying()) pause() else play()
        ACTION_CANCEL -> {
            //TODO: implement a better and the correct way
            for (client in clients) {
                client.onStop()
            }
            stopSelf()
        }
        else -> showNotification()
    }

    return START_STICKY
}

和我的 activity:

 playerService.clients.add(object : PlayerService.Client {
        override fun onStop() {
            finish()
        }
    })

这是我的自定义通知:

private fun getCustomNotification(name: String, showMainActivity: Intent): Notification {
    /*
        some codes to prepare notificationLayout
     */
    notificationLayout.setTextViewText(R.id.tv_name, name)
    notificationLayout.setImageViewResource(R.id.btn_play, playDrawableID)
    notificationLayout.setOnClickPendingIntent(R.id.btn_play, playPendingIntent)
    notificationLayout.setOnClickPendingIntent(R.id.btn_cancel, cancelPendingIntent)

    // Apply the layouts to the notification
    return NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.logo_mouj)
            .setCustomContentView(notificationLayout)
            .setContentIntent(PendingIntent.getActivity(this, 0, showMainActivity, 0))
            .setOngoing(true)
            .build()

}

试试这个

fun stopServices() {
    val serviceIntent = Intent(this, NotificationService::class.java)
    serviceIntent.action = Constants.ACTION.STOPTFOREGROUND_ACTION
    stopService(serviceIntent)
}

在你的activity中调用这个方法

在您的服务启动命令中

 ACTION.STOPFOREGROUND_ACTION -> {
            //Toast.makeText(this, "Service Stoped", Toast.LENGTH_SHORT).show();
            stopForeground(true);
            stopSelf();
            Intent intents = new Intent("com.app.package.ACTION_STOP");
            LocalBroadcastManager.getInstance(this).sendBroadcast(intents);
            //startActivity(new Intent(this,MainActivity.class));

        }



val notificationLayout= new RemoteViews(getPackageName(), R.layout.custom_layout);


 Intent closeIntent = new Intent(this, NotificationService.class);
    closeIntent.setAction(Constants.ACTION.STOPFOREGROUND_ACTION);
    PendingIntent cancelPendingIntent= PendingIntent.getService(this, 0, closeIntent, 0);

点击关闭按钮

    notificationLayout.setOnClickPendingIntent(R.id.btn_cancel, cancelPendingIntent);

如果这个任务看起来令人沮丧,那可能是因为这是一个不寻常的用例,并且在某种程度上超出了 Android 设计者的想法。您可能想问问自己是否有更多 "Android-like" 方式来实现您的目标(即 无需 枚举绑定客户端)。

考虑您的要求:

  1. 播放器应该在用户与应用交互时可用(通过 Activity)
  2. 当用户在后台运行应用程序时,播放器应该继续存在
  3. 当用户点击通知时播放器应该停止
  4. 当用户使用完该应用程序后,应用程序应该得到清理

我认为您可能选择了通过绑定到 Service 来开始播放,并通过 onDestroy()-ing 结束播放。这是设计错误,它会给你带来很多问题。毕竟,根据 docs,一个本地进程中 Service 实际上只代表 "an application's desire to perform a longer-running operation while not interacting with the user" 而它 "is not a means itself to do work off of the main thread."

相反,通过将每个 Activity 保持在 onStart()onStop() 之间,在播放开始时使用 startService(),在播放时使用 stopSelf()停止,您将自动完成目标 1、2 和 4。当应用程序处于所有 Activities 都在屏幕外的状态时,它将有 0 个绑定客户端。如果除此之外,没有 startService() 未完成,则它是 OS 立即终止的候选者。在任何其他状态下,OS 都会保留它。

这没有任何额外的花哨逻辑来跟踪绑定 Activities。当然,您可能希望在播放器播放时 startForeground(),在播放结束时 stopForeground()(大概您已经处理了该细节)。

这留下了如何实现目标 3 的问题。要从 Activities 或通知中影响 Service 中的状态更改,您只需使用 Intents(就像您已经使用的那样)。在这种情况下,Service 将通过 stopSelf() 管理自己的生命周期。这再次假设您在适当的时间使用 startForeground() (Oreo+),否则 Android 可能会由于 background execution limits.

而终止您的应用程序

事实上,如果您选择使用 Intents,您可能会发现您甚至根本不需要将 Activities 绑定到 Service(IOW,目标#1 在这种情况下无关紧要)。如果您需要直接引用该服务(通过 Binder),您只需要绑定到 Service