当前台服务最后停止时,应用程序保持运行


9

我遇到了Android的流程管理中与前台服务结合在一起的行为,这确实使我感到困惑。

什么对我来说合理

  1. 当您从“最近使用的应用程序”中滑动应用程序时,操作系统应在不久的将来完成应用程序的处理。
  2. 当您在运行前台服务时从“最近的应用程序”中滑动应用程序时,该应用程序仍处于活动状态。
  3. 当您从“最近使用的应用程序”中滑动应用程序之前停止前台服务时,您将获得与1)相同的功能。

是什么让我感到困惑

当您在前台没有任何活动的情况下停止前台服务(应用程序未出现在“最新应用程序”中)时,我希望该应用程序现在被杀死。

但是,这没有发生,应用程序过程仍然有效。

我创建了一个最小的示例来展示这种行为。

ForegroundService:

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import timber.log.Timber

class MyService : Service() {

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onCreate() {
        super.onCreate()
        Timber.d("onCreate")
    }

    override fun onDestroy() {
        super.onDestroy()
        Timber.d("onDestroy")

        // just to make sure the service really stops
        stopForeground(true)
        stopSelf()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Timber.d("onStartCommand")
        startForeground(ID, serviceNotification())
        return START_NOT_STICKY
    }

    private fun serviceNotification(): Notification {
        createChannel()

        val stopServiceIntent = PendingIntent.getBroadcast(
            this,
            0,
            Intent(this, StopServiceReceiver::class.java),
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("This is my service")
            .setContentText("It runs as a foreground service.")
            .addAction(0, "Stop", stopServiceIntent)
            .build()
    }

    private fun createChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager = getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(
                NotificationChannel(
                    CHANNEL_ID,
                    "Test channel",
                    NotificationManager.IMPORTANCE_DEFAULT
                )
            )
        }
    }

    companion object {
        private const val ID = 532207
        private const val CHANNEL_ID = "test_channel"

        fun newIntent(context: Context) = Intent(context, MyService::class.java)
    }
}

BroadcastReceiver停止服务:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent

class StopServiceReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {

        val serviceIntent = MyService.newIntent(context)

        context.stopService(serviceIntent)
    }
}

活动:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startService(MyService.newIntent(this))
    }
}

清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.christophlutz.processlifecycletest">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".MyService"/>
        <receiver android:name=".StopServiceReceiver" />
    </application>

</manifest>

尝试以下方法:

  1. 启动应用程序,停止前台服务,从“最近的应用程序”中删除应用程序
  2. 启动应用程序,从“最新应用程序”中删除应用程序,停止前台服务

您可以在Android Studio的LogCat中看到,案例1的应用进程被标记为[DEAD],案例2则没有。

由于复制非常容易,因此可能是预期的行为,但是我在文档中没有发现任何真正的提及。

有人知道这是怎么回事吗?

Answers:


0

这取决于前台服务的实际功能。如果它使用线程,网络连接,文件I / O等消耗内存的活动,即使您尝试停止该服务,它也不会被破坏,因此该过程将继续存在。这还包括在尝试停止服务时仍保持活动状态的所有接口回调。特别是那些仍在运行(甚至中断)并且绑定了服务的线程会阻塞生命周期,从而无法正常停止该服务。

确保服务中没有内存泄漏,并关闭每个连接(数据库,网络等),从所有接口中删除所有回调,然后再停止服务。您可以确保如果onDestroy()调用该服务将被销毁。

对于不同的流程:我认为流程保持活动的原因是,系统认为服务可以在一段时间内重新启动,因此可以使流程保持较短的时间。至少,这就是我所观察到的,因为即使在onDestroy()调用之后,该进程仍然有效,我仍然可以从调试器中看到它。

如果您要确保(即使不建议采用这种方式)确保在一切都销毁之后将进程杀死,则可以随时调用此命令终止进程本身:

android.os.Process.killProcess(android.os.Process.myPid());

您可以从问题中重现该示例的行为-没有连接,线程或任何其他保持运行的状态。onDestroy(服务)被调用,但是在一切都消失之后,该过程仍然有效。即使操作系统在重新启动该服务的情况下仍使进程保持活动状态,我也看不出为什么先停止该服务然后从最近的应用程序中删除该应用程序时,它却没有做同样的事情。所显示的行为似乎不太直观,尤其是考虑到最近对后台执行限制的更改,因此很高兴知道如何确保该过程被停止
David Medenjak

@DavidMedenjak尝试使用getApplicationContext().startService(MyService.newIntent(this));而不是正在使用的东西,并告诉我结果。我相信活动仍然存在,因为使用了活动上下文而不是应用程序上下文。另外,如果您要在Oreo或更高版本上进行测试,请尝试使用getApplicationContext().startforegroundService(MyService.newIntent(this));
Furkan Yurdakul,

0

Android系统以其在内存,处理器能力和应用程序进程生命周期方面的自我意识而闻名-它自行决定是否杀死一个进程(与活动和服务相同)

是有关此问题的官方文档。

看看它对前景所说的

系统中将永远只有少数这样的进程,并且只有在内存太低以至于这些进程都无法继续运行的情况下,这些进程才最终被杀死。通常,在这一点上,设备已达到内存分页状态,因此需要此操作才能使用户界面保持响应状态。

可见的流程(前台服务是可见的流程)

与前台进程相比,系统中运行的这些进程的数量不受限制,但仍受到相对控制。这些进程被认为非常重要,除非必须这样做才能使所有前台进程保持运行,否则它们不会被杀死。

这意味着只要有足够的内存来支持所有前台进程,Android OS就会运行您的应用程序进程。即使您停止了它,系统也可能只是将其移至缓存的进程并以类似队列的方式进行处理。最终,它会以任何一种方式被杀死-但通常它不是由您决定的。坦白说,在所有Android生命周期方法被调用之后(只要调用了),您就根本不必关心应用程序进程发生了什么。Android更了解。

当然,您可以终止该过程,android.os.Process.killProcess(android.os.Process.myPid());但是不建议您这样做,因为它会中断正确的Android元素生命周期,并且可能不会调用正确的回调,因此,在某些情况下,您的应用可能行为不当。

希望能帮助到你。


0

在Android上,由操作系统决定要杀死哪些应用程序。

有一个优先级顺序:
具有前台服务的应用程序很少被杀死,相反,在一段时间不活动之后,其他应用程序和服务也被杀死,这就是应用程序发生的情况。

当您完成前台服务时,这增加了系统终止您的应用程序的可能性,但绝不意味着它将立即被终止。


0

最近通话屏幕 [...]是一个系统级的用户界面,列出了最近访问过的活动任务

您可以在列表中的单个应用程序中进行多个活动或任务。这是不是一个最近访问的应用列表。

因此,“ 最新消息”屏幕的元素与应用程序过程之间没有直接关联。

无论如何,如果您关闭了应用程序的上一个活动,并且在流程中没有任何其他内容(例如前台服务)运行,则该流程将被清理。

因此,当您停止正在运行的前台(通过stopService()stopSelf()和解除绑定)时,系统还将清理其正在运行的进程。

因此,这确实是一种预期的行为

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.