【问题标题】:Continue Service even if application is cleared from Recent app即使应用程序从最近的应用程序中清除,也继续服务
【发布时间】:2015-01-06 16:34:10
【问题描述】:

我有一个小问题。

在我的应用程序中,用户成功登录后会启动一个服务。以前,如果应用程序被终止,服务需要停止。 (比如说,通过滑动从最近的应用程序列表中删除。)所以我们使用了android:stopWithTask="true"。现在我们需要服务按原样运行,即使启动它的任务已从最近的应用程序列表中删除。所以我将服务更改为包含android:stopWithTask="false"。但这似乎不起作用。

相关代码:

这里是与Service相关的manifest部分:

<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 是扩展Sevrice 的自定义类:

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 就会启动。之后我按下主页按钮,所以应用程序被移到后台。现在我从最近的应用程序列表中删除该应用程序。那时,我应该看到 Toast 和控制台消息,按照这个方法的描述:

public void onTaskRemoved (Intent rootIntent)

在 API 级别 14 中添加

如果服务当前正在运行并且用户拥有 删除了来自服务应用程序的任务。如果你有 设置 ServiceInfo.FLAG_STOP_WITH_TASK 那么你将不会收到这个 打回来;相反,服务将被停止。

参数 rootIntent 原来的根 Intent 用于 启动要删除的任务。

但我没有看到任何这些。服务在onStartCommand 中返回START_STICKY,所以我认为onTaskRemoved 应该与标志android:stopWithTask="false" 一起被触发。

我错过了什么吗?

如果我需要添加一些可能对找出问题很重要的代码,请告诉我。

P.S.:到目前为止,我在 4.2.2 上对此进行了测试。

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

我应该怎么做才能在所有版本中都能正常工作?

【问题讨论】:

  • 以防万一,您是如何启动此服务的?如果通过bindService(),那么当客户端(例如Activity)解除绑定时Service会自动销毁,除非你也显式调用了startService()
  • AFAIK,只有一种方法可以从其onStopService()再次启动服务
  • @matiash 在其他版本中工作。可能是 Karbonn 或 4.2.2 中的问题,谢谢。 :)
  • 谢谢你,我确实解决了你的问题,得到了线索:)

标签: android android-service


【解决方案1】:

如果可以在服务运行时发出通知,可以使用 startForegroundService 和 startForeground 来完成。

有三个重要的技巧:

  1. 调用 startForegroundService,它会创建一个不限于绑定上下文的长时间运行的服务,并承诺稍后调用 startForeground。
  2. 在 onStartComand 中返回 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】:

    只要遵循这些场景,您的服务和流程(线程在您的服务中运行)将保持连续。

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

      @Override
      public int onStartCommand(final Intent intent, 
                                final int flags,
                                final int startId) {
      
          //your code
          return START_STICKY;
      }  
      
    2. 如果您的应用程序从最近的应用程序中删除,上述代码将在被破坏时重新启动服务并始终保持运行,但从服务运行的进程(线程)将停止工作。为确保您的进程(线程)始终处于运行状态,您必须重写 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);
      }
      
    3. 如下启动服务

    startService(new Intent(this, YourService.class));

    【讨论】:

    • 关于 1) 的答案,developer.android.com/reference/android/app/… 请按 ctrl+f 并搜索以下文本以获得完整含义。 “这样做的一个重要结果是,如果您实现 onStartCommand() 来安排异步或在另一个线程中完成的工作,那么您可能希望使用 START_FLAG_REDELIVERY 让系统为您重新交付 Intent ”a) 虽然以下是对于流程生命周期,使用“START_STICKY”是为了服务生命周期,现在我们应该使用什么? b) “为您交付 Intent”是什么意思?
    • 通过您使用 onTaskRemoved( ) 的解决方案,我注意到进程和服务正在重新启动。这意味着,进程和服务会进入几秒钟内无法完成工作的状态。但即使用户在最近的应用程序列表中向右滑动应用程序,我也希望服务和进程运行 24 x 7。怎么做?请帮忙。
    • @user3705478:你得到解决方案了吗,请分享一下
    • 我应该把no放在哪里。 3 “启动服务如下”代码?
    • 代码:startService(new Intent(this, YourService.class));把它放在你想启动服务的任何地方。主要是我们从 Activity 的 onCreate() 方法启动它,但我们可以从代码中任何需要的地方启动它。 @Woppi
    【解决方案3】:

    在您的服务中,添加以下代码。在 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() 回调触发并且我看到我的服务正在重新启动
    • 这不是一个解决方案,您的服务仍在被杀死并使用它重新启动,这在大多数情况下是不需要的。
    • @Nima 是的。它确实会重新启动,这是我不想要的。您是否为这种情况找到了解决方案?
    • 最近的应用列表中滑动删除应用时,即使在onTaskRemoved()return START_STICKY中添加服务也无法重启
    【解决方案4】:

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

    【讨论】:

    • 谢谢尼玛。你能详细说明一下吗?甚至更好,一些很好的例子会很棒。谢谢
    • @mauron,您所做的是扩展Application 类,然后在该类中覆盖onCreate() 并在其中使用startService() 启动服务,然后绑定到它。通过不将其设置为 null 来保持活页夹变量处于活动状态。
    • 有人测试过这个解决方案吗?我试过了,什么都没有。
    • 这听起来像是服务对内存的不必要使用。尤其是 Android 文档说:~“启动的服务必须管理自己的生命周期。
    • 不确定您所说的不必要的内存使用是什么意思,这与从活动绑定到服务有什么不同?并且你不需要从 App 子类管理服务生命周期,只需启动它由子类完成。
    【解决方案5】:

    写下我在服务类的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);
        }
    
    }
    

    【讨论】:

    • 请提供某种描述以便于理解
    • 当应用程序从任何地方被杀死时,AlarmManager 会被清除。所以这不是一个解决方案。
    • @PankajKumar 我个人使用它,它工作正常,尝试一次然后告诉
    • @UnityBeginner:如果我知道你在应用程序中使用它,我永远不会安装它。因为你每秒钟都在唤醒我的设备,而你是我电池耗尽的原因
    • 同意它消耗电池这么多,但它不会让服务停止
    【解决方案6】:

    似乎将应用程序从“最近的任务”中删除会杀死所有附加的内容。

    也许你应该去那里看看,如果服务停止,你应该找到一种重新启动服务的方法: https://stackoverflow.com/a/22464640/4232337

    【讨论】:

    • 这不适用于前台服务。从最近刷卡后,它们将保持不变...
    猜你喜欢
    • 2019-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-13
    • 2019-12-05
    • 1970-01-01
    相关资源
    最近更新 更多