Android:在应用程序被关闭时保持服务运行

121

我想在应用被杀死时仍然让一个 IntentService 在后台运行。而且当我说“被杀死”时,指的是 长按Home键 -> 查看所有正在运行的应用 -> 将我的应用向一边滑动 -> 应用被杀死 或者 长按返回键 -> 应用被杀死

我的代码如下,在我的MainActivity中:

Intent intent = new Intent(this, MyService.class);
this.startService(intent);

在我的 MyService 中:

public class MyService extends IntentService {

@Override
protected void onHandleIntent(Intent intent) {
    System.out.println("MyService started");
    run();
}

private void run() {
    while (true){
        System.out.println("MyService still running");
        doSomething();
        waitSomeTime();
    }
}

}

我发现当应用程序打开时,服务正在运行。当我通过主屏幕按钮最小化应用程序时,它仍在运行。当我通过返回按钮关闭应用程序时,它仍在运行。但是如果我像上面提到的那样终止它,它将停止运行。我该如何解决这个问题?


请参考以下内容:http://androidtrainningcenter.blogspot.in/2013/05/how-to-make-android-service-unstoppable.html - Ankit Sharma
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - user2078872
这个回答解决了你的问题吗?当应用关闭时,Android服务停止 - Josh Correia
也许我的答案对某些人有用:https://stackoverflow.com/a/64113820/2877427 - AnasSafi
4
这些天在所有答案中实际起作用的是什么? - Ahmad
9个回答

123

所有的答案似乎都是正确的,所以我将在这里给出一个完整的答案。

首先,在Android中启动一个Broadcast是实现你尝试做的事情的最简单方法,当应用程序被手动终止时,定义一个自定义的BroadcastReceiver来触发服务重启。

现在让我们跳入代码。


YourService.java中创建您的服务

请注意onCreate()方法,在其中为版本大于Android Oreo的Build以不同的方式启动前台服务。这是因为最近引入了严格的通知政策,我们必须定义自己的通知渠道才能正确显示它们。

onDestroy()方法中的this.sendBroadcast(broadcastIntent);语句是异步发送具有操作名称"restartservice"的广播的语句。稍后我们将使用它作为触发器来重启我们的服务。

在这里,我们定义了一个简单的计时器任务,每1秒钟Log中打印一次计数器值,并在每次打印时将其自身递增。

public class YourService extends Service {
public int counter=0;

    @Override
    public void onCreate() {
        super.onCreate();
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
            startMyOwnForeground();
        else
            startForeground(1, new Notification());
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private void startMyOwnForeground()
    {
        String NOTIFICATION_CHANNEL_ID = "example.permanence";
        String channelName = "Background Service";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        assert manager != null;
        manager.createNotificationChannel(chan);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        Notification notification = notificationBuilder.setOngoing(true)
                .setContentTitle("App is running in background")
                .setPriority(NotificationManager.IMPORTANCE_MIN)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        startForeground(2, notification);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        startTimer();
        return START_STICKY;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        stoptimertask();

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
    }



    private Timer timer;
    private TimerTask timerTask;
    public void startTimer() {
        timer = new Timer();
        timerTask = new TimerTask() {
            public void run() {
                Log.i("Count", "=========  "+ (counter++));
            }
        };
        timer.schedule(timerTask, 1000, 1000); //
    }

    public void stoptimertask() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

创建一个广播接收器,以响应在 Restarter.java 中自定义的广播

你刚才在 YourService.java 中自定义了一个 action 名为"restartservice" 的广播,现在这个广播将会触发一个方法来重启你的服务。在 Android 中,我们使用BroadcastReceiver 来实现。

我们覆盖内置的 BroadcastReceiver 中的 onReceive() 方法,并添加语句以重新启动服务。由于在 Android Oreo 8.1 及以上版本中存在严格的后台策略,因此使用 startService()无法正常工作,一旦应用程序被杀死,服务将很快终止重启。因此,我们在高版本中使用 startForegroundService() 并显示持续通知以保持服务运行。


public class Restarter extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("Broadcast Listened", "Service tried to stop");
        Toast.makeText(context, "Service restarted", Toast.LENGTH_SHORT).show();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(new Intent(context, YourService.class));
        } else {
            context.startService(new Intent(context, YourService.class));
        }
    }
}

在应用启动时定义MainActivity.java调用服务。

这里我们定义了一个单独的isMyServiceRunning()方法来检查后台服务的当前状态。如果服务没有在运行,则使用startService()启动它。

由于应用已经在前台运行,因此我们不需要将服务作为前台服务启动,以防止它被终止。

请注意,在onDestroy()中,我们专门调用stopService(),以便调用我们修改后的覆盖方法。如果不这样做,当应用被杀死时,服务将会自动结束,而不会调用我们修改过的YourService.java中的onDestroy()方法。

public class MainActivity extends AppCompatActivity {
    Intent mServiceIntent;
    private YourService mYourService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mYourService = new YourService();
        mServiceIntent = new Intent(this, mYourService.getClass());
        if (!isMyServiceRunning(mYourService.getClass())) {
            startService(mServiceIntent);
        }
    }

    private boolean isMyServiceRunning(Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                Log.i ("Service status", "Running");
                return true;
            }
        }
        Log.i ("Service status", "Not running");
        return false;
    }


    @Override
    protected void onDestroy() {
        //stopService(mServiceIntent);
        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
        super.onDestroy();
    }
}

最后,在AndroidManifest.xml中注册它们

上面的三个类都需要分别在AndroidManifest.xml中进行注册。

请注意,我们使用操作名称"restartservice"定义了一个带有intent-filter的标签,其中将Restarter.java注册为一个receiver。 这确保了每当系统遇到具有给定操作名称的广播时,都会调用我们的自定义BroadcastReciever

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <receiver
        android:name="Restarter"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="restartservice" />
        </intent-filter>
    </receiver>

    <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="YourService"
        android:enabled="true" >
    </service>
</application>

如果应用程序被从任务管理器中终止,那么这将重新启动您的服务。 只要用户没有从应用程序设置中的强制停止应用程序,此服务将在后台继续运行。

更新:感谢Dr.jacky指出这一点。上述方法仅在服务的onDestroy()被调用时才会起作用,而这有时可能不是情况,我之前不知道。谢谢。


2
仍然存在问题,当应用程序在Oreo中被杀死或处于后台时,它无法在后台工作。它只能在应用程序处于前台时工作。请帮忙解决这个问题。 - Rama Tulasi
1
你是在MIUI上测试吗?如果是的话,很抱歉告诉你,他们有一个严格的后台任务策略,会无条件地杀死所有未被列入白名单的应用程序。 - Sayan Sil
8
жњЌеЉЎзљ„onDestroyж–№жі•еЏЇиѓЅдёЌдјљиў«и°ѓз”Ёпјљhttps://dev59.com/questions/R2w05IYBdhLWcg3wcReu#7237522 - Dr.jacky
2
谢谢!我更新了我的回答以确保大家知道这个可能性。今天我学到了新东西。 - Sayan Sil
5
如果我们已经从onStartCommand()中返回了START_STICKY,这意味着如果服务被杀死,操作系统将重新启动该服务。那么为什么我们需要在服务的onDestroy()中启动一个广播来启动服务呢?难道重新启动不是由操作系统处理的吗? - DevAndroid
显示剩余9条评论

55

如果您的服务是由您的应用程序启动的,则实际上您的服务正在主进程中运行。因此,当应用程序被终止时,服务也将停止。所以您可以在服务的onTaskRemoved方法中发送广播,如下所示:

 Intent intent = new Intent("com.android.ServiceStopped");
 sendBroadcast(intent);

并且有一个广播接收器,它将再次启动一个服务。我已经尝试过了。服务从所有类型的终止中重新启动。


25
这是正确的答案。我在一篇博客文章中写了一个简单的示例。 - FabioC
1
请告诉我在意图中写的是什么意思,即“com.android.ServiceStopped”是什么意思,我调用了哪个意图在sendBroadcast方法中创建一个新的意图来停止服务。 - Ahmad Joyia
1
@BhendiGawaar 我按照你说的做了同样的事情。但是在像vivo、OPO、MIUI等定制化的操作系统手机上却无法工作。 - Rohan Shinde
5
有时候,当安卓系统指定需要杀掉一些进程以释放更多的内存时,它甚至不会从服务中调用"onTaskRemoved"。它只是将其关闭。 - Mahdi-Malv
4
我使用的是联想TB3-730X。当用户终止应用程序时,onTaskRemoved没有被调用,我也尝试了onDestroy,但它也不起作用。请建议我任何解决方案。 - Megala Mani
显示剩余2条评论

19

在您的服务中,添加以下代码。

@Override
public void onTaskRemoved(Intent rootIntent){
    Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
    restartServiceIntent.setPackage(getPackageName());

    PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
    AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(
    AlarmManager.ELAPSED_REALTIME,
    SystemClock.elapsedRealtime() + 1000,
    restartServicePendingIntent);

    super.onTaskRemoved(rootIntent);
 }

2
为了消除“缺少PendingIntent可变性标志”警告,请将PendingIntent.FLAG_ONE_SHOT替换为PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE - Donald Duck

6
在onstart命令中加入START_STICKY...这个服务不会被终止,除非它正在执行过多的任务并且内核想要终止它...
@Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i("LocalService", "Received start id " + startId + ": " + intent);
            // We want this service to continue running until it is explicitly
            // stopped, so return sticky.
            return START_STICKY;
        }

通过任务管理器可以杀死程序,但是服务无法在“长时间按下返回按钮”后继续运行 :/ - user2078872
启动粘性服务将在应用程序关闭后再次运行该服务,因此从某种意义上说它将重新启动该服务。 - Fahad Alkamli
10
如果用户关闭了应用程序,也许我们应该尊重用户并让服务停止。 - Marcel Falliere

6
这是因为您正在尝试使用IntentService。以下是来自API文档的相关代码:

IntentService 的功能如下:

在处理完所有启动请求后停止服务,因此您无需调用 stopSelf()。

因此,如果您希望服务无限运行,请将其扩展为 Service 类。但是这并不能保证您的服务会无限运行。如果您的服务优先级较低,内核在内存不足时仍有可能杀死该服务。所以您有两个选择:
1)通过调用 startForeground() 方法使其在前台运行。
2)在服务被杀死后重新启动该服务。以下是文档中关于在服务被杀死后重新启动服务的部分示例。
 public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the 
      // start ID so we know which request we're stopping when we finish the job 
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart 
      return START_STICKY;
  }  

2
在你的服务类中使用 onTaskRemoved。
 @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(AlarmManager.ELAPSED_REALTIME, elapsedRealtime() + 500,
                restartServicePendingIntent);
        Log.d("taskremoved", "task removed ");
        super.onTaskRemoved(rootIntent);
    }

创建 PendingIntent 时,要求指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE 中的一个选项,才能支持 S+(版本 31 及以上)。 - Mia

1

您可以在清单文件中使用android:stopWithTask="false",这意味着即使用户通过从任务列表中删除应用程序来终止它,您的服务也不会停止。

 <service android:name=".service.StickyService"
                  android:stopWithTask="false"/>

44
默认情况下它是假的! - Kishore Jethava

0

你可以尝试以下代码:

public class HeartbeartService extends Service {

private static final int SERVICE_NOTIFICATION_ID = 54321;
private static final String CHANNEL_ID = "Notification service";
private final LocalBinder mBinder = new LocalBinder();

Handler handler = new Handler();
private Runnable runnableCode = new Runnable() {
    @Override
    public void run() {
        Context context = getApplicationContext();

        Intent myIntent = new Intent(context, HeartbeatEventService.class);
        context.startService(myIntent);
        HeadlessJsTaskService.acquireWakeLockNow(context);
        handler.postDelayed(this, 2000);
        
    }
};

public class LocalBinder extends Binder {
    public HeartbeartService getService() {
        return HeartbeartService.this;
    }
}

private void createNotificationChannel() {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is new and not in the support library
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        int importance = NotificationManager.IMPORTANCE_MIN;
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Notification service", importance);
        channel.setDescription("CHANEL DESCRIPTION");
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}





@Override
public void onCreate() {
    super.onCreate();
   
}

@Override
public void onDestroy() {
    super.onDestroy();
    this.handler.removeCallbacks(this.runnableCode);
   
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    this.handler.post(this.runnableCode);
     createNotificationChannel();
     Intent notificationIntent = new Intent(this, MainActivity.class);
     PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent,
             PendingIntent.FLAG_CANCEL_CURRENT);
     Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
             .setContentTitle("Notification service")
             .setContentText("Running...")
             .setBadgeIconType(0)
             .setAutoCancel(false)
             .setOngoing(true)
             .build();

    startForeground(1, notification);
  
    return START_STICKY;
}

}


please add explanation. - Ajay Chauhan
你需要解释什么吗?希望能对你有所帮助。Ajay Chauhan - Apple Yellow

0
这里是我如何在应用程序在后台或甚至被杀死时使用前台服务触发我的警报的方法:
  // Aware user about the foreground service
   private fun setForeGroundNotification() {
    val intent = Intent(this, AlarmForegroundService::class.java)
 
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent)
    } else {
        startService(intent)
    }
}


 

public class AlarmForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    final String CHANNELID = "Foreground Service ID";
    NotificationChannel channel;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        channel = new NotificationChannel(
                CHANNELID,
                CHANNELID,
                NotificationManager.IMPORTANCE_LOW
        );

        getSystemService(NotificationManager.class).createNotificationChannel(channel);
        Notification.Builder notification = new Notification.Builder(this, CHANNELID)
                .setContentTitle("App is running in background to check alarm")
                .setContentText("Checking Alarm..")
                .setAutoCancel(false)
                .setSmallIcon(R.mipmap.logo);
        startForeground(1, notification.build());
    }
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    int hours = intent.getIntExtra("hours", 0);
    int minutes = intent.getIntExtra("minutes", 0);

    //Firing alarm at selected wake up time
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, hours);
    calendar.set(Calendar.MINUTE, minutes);

    //To prevent alarm trigger for past time.
    if (calendar.before(Calendar.getInstance())) {
        calendar.add(Calendar.DATE, 1);
    }

    Intent intent1 = new Intent(this, AlarmReceiver.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            intent1,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
    );

    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), (1000 * 60 * 60 * 24), pendingIntent);
    return super.onStartCommand(intent, flags, startId);
}

}

class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
    val intent = Intent(context, AlarmIntentService::class.java)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context!!.startForegroundService(intent)
    } else {
        context!!.startService(intent)
    }
}

}

public class AlarmIntentService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    NotificationChannel channel = null;
    int notificationId = 123;
    long[] vibPattern = {1000, 1000, 1000, 1000};

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        channel = new NotificationChannel("channel2",
                "Sleep Alarm",
                NotificationManager.IMPORTANCE_HIGH);
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        channel.setVibrationPattern(vibPattern);
    }

    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        manager.createNotificationChannel(channel);
    }

    //Creating the notification object
    NotificationCompat.Builder notification = new NotificationCompat.Builder(this, "channel2");
    notification.setSmallIcon(R.drawable.ic_bell);
    notification.setContentTitle("Wake up Alarm!!");
    notification.setContentText("It's time to get up");
    notification.setAutoCancel(true);
    notification.setSound(null);
    //notification.build().flags |= Notification.FLAG_AUTO_CANCEL;

    //Action button handling
    Intent onDismiss = new Intent(this, DismissReceiver.class);
    onDismiss.putExtra("NOTIFICATION_ID", notificationId);
    Intent onSnooze = new Intent(this, SnoozeReceiver.class);


    SharedPreferences sh = getSharedPreferences("MySharedPref", Context.MODE_PRIVATE);
    int snooze = sh.getInt("snooze", 0);

    PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(this, 1, onDismiss, PendingIntent.FLAG_IMMUTABLE);
    if (snooze == 0) {
        notification.setContentIntent(dismissPendingIntent);
        notification.addAction(R.drawable.ic_cross, "Dismiss", dismissPendingIntent);
    } else {
        PendingIntent snoozePendingIntent = PendingIntent.getBroadcast(this, 1, onSnooze, PendingIntent.FLAG_IMMUTABLE);
        notification.setContentIntent(dismissPendingIntent);
        notification.setContentIntent(snoozePendingIntent);
        notification.addAction(R.drawable.ic_cross, "Dismiss", dismissPendingIntent);
        notification.addAction(R.drawable.ic_cross, "Snooze", snoozePendingIntent);
    }

    //To trigger the chosen alarm ringtone
    Intent startIntent = new Intent(this, RingtonePlayingService.class);
    this.startService(startIntent);

    manager.notify(notificationId, notification.build());
    startForeground(notificationId, notification.build());
}

}


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