Продолжить обслуживание, даже если приложение удалено из приложения Недавние

У меня небольшая проблема.

В моем приложении служба запускается после успешного входа пользователя. Ранее сервис необходимо было остановить, если приложение было убито. (скажем, удалили из списка Недавние приложения, проведя пальцем.) Таким образом, мы использовали android:stopWithTask="true", Теперь нам нужно, чтобы Служба работала как есть, даже если Задача, которая ее запустила, удаляется из списка Недавние приложения. Поэтому я изменил Сервис, чтобы включить 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 - это пользовательский класс, который расширяет 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 и console, как описано в этом методе:

public void onTaskRemoved (намерение rootIntent)

Добавлено в API уровень 14

Это вызывается, если служба в данный момент работает, и пользователь удалил задачу, которая поступает из приложения службы. Если вы установили ServiceInfo.FLAG_STOP_WITH_TASK, вы не получите этот обратный вызов; вместо этого служба будет просто остановлена.

Параметры rootIntent Исходное корневое намерение, которое использовалось для запуска удаляемой задачи.

Но я не вижу ничего из этого. Служба возвращается START_STICKY в onStartCommand, Я так думаю onTaskRemoved должен быть уволен вместе с флагом android:stopWithTask="false",

Я что-то пропустил?

Дайте мне знать, если мне нужно добавить код, который может быть важен, чтобы выяснить, в чем дело.

PS: я проверял это на 4.2.2 до сих пор.

PS: я только что протестировал тот же код в 4.1.2, на котором служба продолжает работать, и я получаю сообщение "onTaskRemoved named" в журнале тоже.

Что я должен сделать, чтобы это работало во всех версиях?

5 ответов

Просто следуйте этим сценариям, ваш сервис и процессы (потоки внутри вашего сервиса) будут оставаться непрерывными.

  1. Создайте сервис и используйте START_STICKY в качестве возвращаемого значения в методе onStartCommand, как показано ниже

         @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(новый Intent(это, YourService.class));

В вашем сервисе добавьте следующий код. Это нормально работает для меня в 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); 
}

Если вы привязываетесь к своему сервису из подкласса класса Application и удерживаете соединение IBinder, сервис останется активным даже после удаления приложения из последних приложений.

Если можно разместить уведомление во время работы службы, вы можете использовать startForegroundService и startForeground для этого.

Есть три важных приема:

  1. Вызовите startForegroundService, который создает долго работающую службу, не ограниченную привязанным контекстом, и обещает вызвать startForeground позже.
  2. Верните START_STICKY в onStartComand
  3. Вызовите startForeground с уведомлением, как обещано в (1).

Например, если вы хотите запустить 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)
        }
    }

}

Похоже, что удаление приложения из "недавних задач" убивает все, что к нему прикреплено.

Возможно, вам следует поискать способ перезапустить службу, если она остановится: /questions/40366702/fonovaya-sluzhba-ostanavlivaetsya-kogda-aktivnost-prekraschaetsya-ostanavlivaetsya-ili-prinuditelno-zakryivaetsya/40366709#40366709

Напишите 5 строк, которые я добавил в oncreate() класса обслуживания

Как это:

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

}
Другие вопросы по тегам