即使应用程序从最近的应用程序中清除,也要继续服务

56

我有一个小问题。

在我的应用程序中,用户成功登录后会启动一个服务。以前,如果应用程序被杀死,也就是说,通过滑动从最近的应用程序列表中删除,服务需要停止。因此,我们使用了 android:stopWithTask =“true”。现在我们需要该服务照常运行,即使启动它的任务已从最近的应用程序列表中删除。因此,我将 Service 更改为包括 android:stopWithTask =“false”。但这似乎不起作用。

相关代码:

下面是与服务相关的清单部分:

<service
    android:enabled="true"
    android:name=".MyService"
    android:exported="false"
    android:stopWithTask="false" />

在MyService.java文件中:

public class MyService extends AbstractService {

    @Override
    public void onStartService() {
        Intent intent = new Intent(this, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new Notification(R.drawable.ic_launcher, "My network services", System.currentTimeMillis());
        notification.setLatestEventInfo(this, "AppName", "Message", pendingIntent);
        startForeground(MY_NOTIFICATION_ID, notification);  
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Toast.makeText(getApplicationContext(), "onTaskRemoved called", Toast.LENGTH_LONG).show();
        System.out.println("onTaskRemoved called");
        super.onTaskRemoved(rootIntent);
    }
}

AbstractService.java 是一个扩展了 Service 的自定义类:

public abstract class AbstractService extends Service {

    protected final String TAG = this.getClass().getName();

    @Override
    public void onCreate() {
        super.onCreate();
        onStartService();
        Log.i(TAG, "onCreate(): Service Started.");
    }

    @Override
    public final int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStarCommand(): Received id " + startId + ": " + intent);
        return START_STICKY; // run until explicitly stopped.
    }

    @Override
    public final IBinder onBind(Intent intent) {
        return m_messenger.getBinder();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        onStopService();
        Log.i(TAG, "Service Stopped.");
    }    

    public abstract void onStartService();
    public abstract void onStopService();
    public abstract void onReceiveMessage(Message msg);

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Toast.makeText(getApplicationContext(), "AS onTaskRemoved called", Toast.LENGTH_LONG).show();
        super.onTaskRemoved(rootIntent);
    }
}
如果我登录应用程序,MyService将启动。之后我按Home按钮,应用程序就会移动到后台。现在我从“最近使用的应用程序”列表中删除应用程序。此时,根据此方法的描述,我应该看到Toast和控制台消息:

 

public void onTaskRemoved (Intent rootIntent)

   

API级别14中添加

   

如果服务当前正在运行并且用户已删除来自服务应用程序的任务,则会调用此操作。如果您设置了ServiceInfo.FLAG_STOP_WITH_TASK,则不会收到此回调;相反,服务将被简单地停止。

   

参数rootIntent:启动要删除的任务的原始根Intent。

但我没有看到任何东西。 Service在onStartCommand中返回START_STICKY,因此我认为onTaskRemoved应该与标志android:stopWithTask ="false"一起触发。

我错过了什么吗?

请告诉我,在我需要添加一些可能很重要以找出问题所在的代码的情况下。

P.S.:我目前在4.2.2上进行了测试。

P.S.:我刚刚在4.1.2中测试了相同的代码,服务继续运行,我也在日志中收到了消息“ onTaskRemoved called”。

我该怎么做才能使其在所有版本上正常工作?


1
以防万一,你是通过什么方式启动这个服务的?如果是通过bindService()方法启动的话,那么当客户端(例如Activity)解除绑定时,该服务会自动销毁,除非你还显式地调用了startService()方法。 - matiash
据我所知,从onStopService()重新启动服务只有一种方法。 - Mehul Joisar
@matiash 在其他版本中工作。可能是Karbonn或4.2.2的问题。谢谢。 :) - MysticMagicϡ
请帮助我 https://stackoverflow.com/questions/53705956/background-service-pause-and-start-again-automatically-on-oreo - user8919158
谢谢,我根据你的问题进行了修复,有头绪了 :) - Ramkesh Yadav
6个回答

36

只需按照以下步骤,您的服务和进程(线程在您的服务内运行)将保持连续。

  1. 创建服务并在onStartCommand方法中使用START_STICKY作为返回值,如下所示:

@Override
public int onStartCommand(final Intent intent, 
                          final int flags,
                          final int startId) {

    //your code
    return START_STICKY;
}  
  • 如果服务被销毁,上面的代码将重新启动该服务并始终保持运行状态,但如果您的应用程序从最近使用的应用程序中删除,则从服务运行的进程(线程)将停止工作。为确保您的进程(线程)始终处于运行状态,您必须重写onTaskRemoved() 方法,并添加重新启动任务的代码,如下所示。

  • @Override
    public void onTaskRemoved(Intent rootIntent){
        Intent restartServiceTask = new Intent(getApplicationContext(),this.getClass());
        restartServiceTask.setPackage(getPackageName());    
        PendingIntent restartPendingIntent =PendingIntent.getService(getApplicationContext(), 1,restartServiceTask, PendingIntent.FLAG_ONE_SHOT);
        AlarmManager myAlarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        myAlarmService.set(
                AlarmManager.ELAPSED_REALTIME,
                SystemClock.elapsedRealtime() + 1000,
                restartPendingIntent);
    
        super.onTaskRemoved(rootIntent);
    }
    
  • 按以下方式启动服务
  • startService(new Intent(this, YourService.class));


    1
    关于问题1)的答案,请访问https://developer.android.com/reference/android/app/Service.html#ProcessLifecycle请使用Ctrl + F搜索以下文本以获取完整含义。“这个的一个重要后果是,如果您实现了onStartCommand()来异步或在另一个线程中安排工作,则可能希望使用START_FLAG_REDELIVERY来让系统重新传递Intent。”a)虽然以下内容是关于进程生命周期的,而使用“START_STICKY”是关于服务生命周期的,但现在我们应该使用什么?b)“为您传递Intent”的意思是什么? - sofs1
    5
    通过使用onTaskRemoved()的解决方案,我注意到进程和服务正在重新启动。这意味着,进程和服务进入了一种状态,在几秒钟内无法执行工作。但我希望即使用户在最近使用的应用程序列表中向右滑动应用程序,服务和进程也能运行24 x 7。如何做到这一点?请帮忙。 - sofs1
    1
    @user3705478:你得到解决方案了吗?如果是,请分享一下。 - Fahad
    我应该把第三个“像下面这样启动服务”的代码放在哪里? - Woppi
    这种方法对于像华为和小米这样的设备不起作用,因为它们有一个针对你的应用的电池节省设置。你必须手动禁用这个电池优化才能让它工作。 - joao2fast4u
    显示剩余3条评论

    8
    在您的服务中,添加以下代码。在4.4.2上测试通过。
    这是一个解决办法,可以很好地重新启动服务,如果其进程在关闭应用程序时被杀死。
     @Override public void onTaskRemoved(Intent rootIntent){
         Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
    
         PendingIntent restartServicePendingIntent = PendingIntent.getService(
             getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
         AlarmManager alarmService = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
         alarmService.set(ELAPSED_REALTIME, elapsedRealtime() + 1000,
             restartServicePendingIntent);
    
         super.onTaskRemoved(rootIntent); 
    }
    

    当我划掉我的应用程序时,我确实看到了 onTaskRemoved() 回调触发,并且我看到我的服务正在重新启动。 - Someone Somewhere
    2
    这不是一个解决方案,大多数情况下不希望使用此方法来杀死和重新启动您的服务。 - Nima GT
    @Nima 是的。它会重新启动,这不是我想要的。你解决了这种情况吗? - sofs1
    1
    即使在onTaskRemoved()中添加并使用return START_STICKY,当应用程序从最近使用应用列表中被删除时,该服务仍无法重新启动。 - Sabari Karthik
    请帮我解决这个问题:https://stackoverflow.com/questions/53705956/background-service-pause-and-start-again-automatically-on-oreo - user8919158
    那是一个糟糕的解决方案,它会让服务停止一秒钟。如果该应用程序是媒体播放器,用户会感到它停止了。没有音乐应用程序会像这样工作,必须有另一个更好的解决方案。 - Allan Veloso

    1
    如果您从Application类的子类绑定到您的服务并保持IBinder连接,则即使应用程序从最近使用的应用程序中删除,该服务也将保持活动状态。

    3
    谢谢,Nima。你能详细说明一下吗?或者更好的是,提供一些好的例子会很棒。谢谢。 - mauron85
    2
    @mauron,你所做的是扩展Application类,然后在该类中覆盖onCreate()方法,在其中使用startService()启动服务,然后绑定到它。通过不将绑定变量设置为null来保持其活动状态。 - Someone Somewhere
    1
    有人测试过这个解决方案吗?我尝试了但是没有效果。 - Ricardo Pessoa
    这听起来像是服务不必要的内存使用。特别是Android文档中说:~“启动的服务必须管理自己的生命周期。 - IgorGanapolsky
    不确定您所说的“不必要地使用内存”是什么意思,这与从活动绑定服务有何不同?而且您不需要从App子类中管理服务生命周期,只需启动即可通过子类完成。 - Nima GT
    @Nima,你能提供更多细节吗?也许可以通过在你的回答中添加详细信息和代码来说明问题? - payloc91

    0

    如果在服务运行时放置通知是可以的,您可以使用startForegroundService和startForeground来完成它。

    有三个重要的技巧:

    1. 调用startForegroundService创建一个长时间运行的服务,不受绑定上下文的限制,并承诺稍后调用startForeground。
    2. 在onStartCommand中返回START_STICKY。
    3. 按照(1)中的承诺使用通知调用startForeground。

    例如,如果您想运行一个TimerService,在您的TimerActivity中将执行以下操作:

    private var timerService: TimerService? = null
    
    private val timerServiceConnection = object : ServiceConnection {
    
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            val binder = service as TimerService.Binder
            timerService = binder.getService()
        }
    
        override fun onServiceDisconnected(arg0: ComponentName) {
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        startButton.setOnClickListener {
            timerService?.startTimer(60L, 0L)
        }
    }
    
    override fun onStart() {
        super.onStart()
    
        Intent(this, TimerService::class.java).also {
            ContextCompat.startForegroundService(this, it) // that's the first trick
            bindService(it, timerServiceConnection, Context.BIND_AUTO_CREATE)
        }
    }
    
    override fun onStop() {
        super.onStop()
        unbindService(timerServiceConnection)
        timerService?.updateNotification(secondsRemaining)
    }
    

    你的TimerService将会是这样的:
    class TimerService : Service() {
    
        private val binder = Binder()
    
        private var serviceLooper: Looper? = null
    
        private var serviceHandler: ServiceHandler? = null
    
        private var timer: CountDownTimer? = null
    
        private val notificationUtil by lazy {
            NotificationUtil(this)
        }
    
        override fun onCreate() {
            HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
                start()
                serviceLooper = looper
                serviceHandler = ServiceHandler(looper)
            }
        }
    
        override fun onBind(intent: Intent?): IBinder? = binder
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            val timerRemaining = intent?.getLongExtra(EXTRA_REMAINING, 0) ?: 0L
            if (timerRemaining != 0L) {
                serviceHandler?.obtainMessage()?.also { msg ->
                    msg.arg1 = startId
                    msg.data.putLong(EXTRA_REMAINING, timerRemaining)
                    serviceHandler?.sendMessage(msg)
                }
            }
    
            return START_STICKY // that's the second trick
        }
    
        override fun onDestroy() {
            super.onDestroy()
            timer?.cancel()
        }
    
        fun startTimer(secondsRemaining: Long, id: Long) {
            updateNotification(secondsRemaining)
    
            Intent(this, TimerService::class.java).apply {
                putExtra(EXTRA_REMAINING, secondsRemaining)
            }.also {
                onStartCommand(it, 0, id.toInt())
            }
        }
    
        fun stopTimer() {
            timer?.cancel()
        }
    
        fun updateNotification(secondsRemaining: Long){
            val notification = NotificationCompat.Builder(this, NotificationUtil.CHANNEL_ID_TIMER)
                    .setSmallIcon(R.drawable.ic_timer)
                    .setAutoCancel(true)
                    .setDefaults(0)
                    .setContentTitle(secondsRemaining.formatSeconds())
                    .setContentText("Timer")
                    .setContentIntent(notificationUtil.getPendingIntentWithStack(this, TimerActivity::class.java))
                    .setOngoing(true)
                    .build()
            startForeground(NotificationUtil.NOTIFICATION_ID, notification) // that's the last trick
        }
    
        private fun sendMessage(remaining: Long) {
            Intent(TimerService::class.java.simpleName).apply {
                putExtra(EXTRA_REMAINING, remaining)
            }.also {
                LocalBroadcastManager.getInstance(this).sendBroadcast(it)
            }
        }
    
        private inner class ServiceHandler(looper: Looper) : Handler(looper) {
    
            override fun handleMessage(msg: Message) {
                val secondsRemaining = msg.data.getLong(EXTRA_REMAINING)
                notificationUtil.showTimerStarted(secondsRemaining)
    
                timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {
    
                    override fun onTick(millisUntilFinished: Long) {
                        Log.i(this::class.java.simpleName, "tick ${(millisUntilFinished / 1000L).formatSeconds()}")
                        updateNotification(millisUntilFinished / 1000)
                        sendMessage(millisUntilFinished / 1000)
                    }
    
                    override fun onFinish() {
                        Log.i(this::class.java.simpleName, "finish")
                        notificationUtil.showTimerEnded()
                        sendMessage(0)
                        stopSelf()
                    }
                }.start()
            }
        }
    
        inner class Binder : android.os.Binder() {
            // Return this instance of LocalService so clients can call public methods
            fun getService(): TimerService = this@TimerService
        }
    
        companion object {
    
            const val EXTRA_REMAINING = "EXTRA_REMAINING"
            const val NOTIFICATION_ID = 1 // cannot be 0
    
            fun Long.formatSeconds(): String {
                val s = this % 60
                val m = this / 60 % 60
                val h = this / (60 * 60) % 24
                return if (h > 0) String.format("%d:%02d:%02d", h, m, s)
                else String.format("%02d:%02d", m, s)
            }
        }
    
    }
    

    -2

    这并不适用于前台服务。在从最近任务中滑动后,它们将保持不变... - IgorGanapolsky

    -4

    在服务类的oncreate()中编写我添加的5行代码

    像这样:

    public class AlarmService extends Service {
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            Intent iHeartBeatService = new Intent(AlarmService.this,
                    AlarmService.class);
            PendingIntent piHeartBeatService = PendingIntent.getService(this, 0,
                    iHeartBeatService, PendingIntent.FLAG_UPDATE_CURRENT);
            AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            alarmManager.cancel(piHeartBeatService);
            alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                    System.currentTimeMillis(), 1000, piHeartBeatService);
    
        }
    }
    

    或者

    试试这个

    public class MyService extends Service{
    
    
        @Override
        public IBinder onBind(Intent intent) {
            // TODO Auto-generated method stub
            return null;
        }
    
        @Override
        public void onCreate() {
            // TODO Auto-generated method stub
            super.onCreate();
            System.out.println("service created");
        }
    
        @SuppressLint("NewApi")
        @Override
        public void onTaskRemoved(Intent rootIntent) {
            // TODO Auto-generated method stub
            System.out.println("onTaskRemoved");
            super.onTaskRemoved(rootIntent);
    
        }
    
        @Override
        @Deprecated
        public void onStart(Intent intent, int startId) {
            // TODO Auto-generated method stub
            super.onStart(intent, startId);
            System.out.println("Service started");
            new Handler().postDelayed(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println("Service is running");
                }
            }, 5000);
        }
    
    }
    

    请提供某种描述以便理解。 - SilentKiller
    14
    当应用程序从任何地方被杀死时,AlarmManager会被清除。因此这不是一个解决方案。 - Pankaj Kumar
    @PankajKumar 我个人使用过这个,它运行良好,请尝试一次然后告诉我。 - nawaab saab
    6
    如果我知道你在一个应用程序中使用它,我永远不会安装它。因为你会让我的设备每秒钟都被唤醒,这就是为什么我的电池被耗得像地狱一样的原因。 - Mehul Joisar
    虽然它会消耗很多电池,但它不会让服务停止。 - nawaab saab
    请帮我解决这个问题:https://stackoverflow.com/questions/53705956/background-service-pause-and-start-again-automatically-on-oreo - user8919158

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接