我们如何防止服务被操作系统杀死?

50

我在我的应用程序中使用 Service,它需要一直运行到我的应用程序卸载,但问题是它会被操作系统杀掉。

我们如何防止它被操作系统杀掉?或者,如果它被杀掉了,我们能否通过编程重启该服务?

11个回答

48
您可以使用startForeground()前台运行服务。

前台服务是用户主动知晓的服务,因此在内存低时不会被系统关闭。

但请注意,前台服务必须为状态栏提供通知(查看此处),并且除非停止服务或将其从前台移除,否则无法取消通知。

注意:这仍然不能绝对保证服务不会在极低的内存条件下被杀死。它只能减少被杀死的可能性。


3
谢谢你的回复Dheeraj,但我不需要服务通知,有没有其他方法可以让服务在不被操作系统杀死的情况下运行?你知道Wake_Lock吗? - Rahul
2
我已经将您提供的链接中的代码合并到我的服务中,其中包含startForeground()。但是我的服务有时也会被操作系统杀死。 - Rahul
@Rahul,你有这个问题的解决方案吗? - PankajAndroid
6
这是 Android 处理低内存情况的方式。如果你真的需要让 Android 不杀死你的服务,最好的方法是将其设为系统应用程序,或在你的 onStartCommand() 方法中让你的服务返回 START_STICKY。这样,如果你的服务被杀死,它将自动排队重新启动。 - Jason John
我已经完成了,仍在关闭它。 - Neon Warge
显示剩余2条评论

21

最近我也遇到了和你一样的问题,但现在我找到了一个好的解决方案。首先,你应该知道,即使你的服务被操作系统杀死,操作系统也会很快调用你的服务的onCreate方法。因此,你可以像这样在onCreate方法中进行一些操作:

@Override
public void onCreate() {
    Log.d(LOGTAG, "NotificationService.onCreate()...");
    //start this service from another class
    ServiceManager.startService();
}
@Override
public void onStart(Intent intent, int startId) {
    Log.d(LOGTAG, "onStart()...");
    //some code of your service starting,such as establish a connection,create a TimerTask or something else
}

"ServiceManager.startService()" 的内容是:

public static void startService() {
    Log.i(LOGTAG, "ServiceManager.startSerivce()...");
    Intent intent = new Intent(NotificationService.class.getName());
    context.startService(intent);
}

然而,这种解决方案仅适用于您的服务被GC杀死的情况。有时候我们的服务可能会被程序管理器(PM)杀死。在这种情况下,您的进程将被杀死,并且您的服务将永远不会重新实例化。因此,您的服务无法重新启动。

但好消息是,当PM杀死您的服务时,它会调用您的onDestroy方法。因此,我们可以在该方法中进行一些操作。

    @Override
public void onDestroy() {
    Intent in = new Intent();
    in.setAction("YouWillNeverKillMe");
    sendBroadcast(in);
    Log.d(LOGTAG, "onDestroy()...");
}

"YouWillNeverKillMe"是自定义操作的字符串。 这种方法最重要的事情是,在发送广播之前不要添加任何代码。因为系统不会等待onDestroy()方法的完成,所以您必须尽快发送广播。 然后在manifest.xml中注册一个接收器:

<receiver android:name=".app.ServiceDestroyReceiver" >
        <intent-filter>
            <action android:name="YouWillNeverKillMe" >
            </action>
        </intent-filter>
    </receiver>

最后,创建一个BroadcastReceiver,在onReceive方法中启动您的服务:

@Override
public void onReceive(Context context, Intent intent) {
    Log.d(LOGTAG, "ServeiceDestroy onReceive...");
    Log.d(LOGTAG, "action:" + intent.getAction());
    Log.d(LOGTAG, "ServeiceDestroy auto start service...");
    ServiceManager.startService();
}

希望这对你有所帮助,抱歉我的英文写得不好。


已在Android 4.0.4上进行了测试,目前看起来很有前途! - Alexandre Lavoie
3
@Alaowan:你将重启代码放置在不同的位置。如果服务被垃圾回收机制或电源管理器杀死,无论哪种情况下如果调用了onDestroy,那么是否最好在onDestroy中直接调用startService,就像[at]androidiseverythingforme提供的答案中所述? - Basher51
我认为你的代码防止服务被GC杀死根本没有意义;必须等待操作系统重新启动服务才能运行代码以重新启动你的服务。当服务已经启动时,服务自己启动的意义何在?我甚至进行了测试并确认它无效。通常情况下,我发现操作系统需要几分钟才能重新启动一个黏性服务。 - Sam
2
你的代码阻止用户停止服务的做法不仅没有好处,反而会带来负面影响,因为我认为用户应该对自己的设备有控制权;如果你试图阻止他们手动终止你的服务,这会让他们感到非常烦恼。我知道在某些情况下可能适用,但这个问题是关于防止操作系统杀死服务的。 - Sam
3
请不要点赞这个回答。这是不正确的。 - blueether
显示剩余2条评论

10

在您的服务类中重写方法onStartCommand()并简单地返回START_STICKY(如“它不是空白”所建议的)。这就是你需要的全部。如果运行您的服务的进程被杀死(例如由于低内存条件),Android系统将自动重新启动它(通常会有一些延迟,如5秒)。

不要再使用onStart()了,因为它已经被弃用了。


1
请注意,这是服务的默认行为,所以我认为您甚至不需要明确执行此操作。 - Sam

9

使用

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //**Your code **
    // We want this service to continue running until it is explicitly
    // stopped, so return sticky.
    return START_STICKY;
} 

参考文档学习Service的生命周期。

编辑添加了方法。


2
Vincent,我使用了你的方法,但我的服务在一段时间后又被操作系统杀掉了。有没有其他方法可以解决这个问题? - Rahul
我使用START_NOT_STICKY。 - yozawiratama
6
我觉得这个描述有误;我认为START_STICKY并不能阻止服务被系统杀掉。我认为它会在服务崩溃或被系统杀掉后重新启动该服务。 - Sam

7
据我所知,onDestroy() 方法仅在服务被明确停止(强制停止)时调用。但是,在服务被操作系统杀死或通过清除最近使用应用列表来结束进程的情况下,该方法不会被调用。在这些情况下,将调用另一个事件处理程序 onTaskRemoved(Intent)。根据此处链接的说法,这是 Android 4.3-4.4 中的一个缺陷。尝试使用以下代码:
public void onTaskRemoved(Intent intent){
super.onTaskRemoved(intent);
Intent intent=new Intent(this,this.getClass()); 
startService(intent); 
}

5

我发现了另一个解决方案,可以保证您的服务始终处于运行状态。在我的情况下,这个方案也解决了FileObserver的问题,它停止工作一段时间后。

  1. 使用一个活动(StartServicesActivity)启动前台服务文件(FileObserverService)。
  2. 使用BroadcastReceiver类(例如CommonReceiver)在某些特殊情况下和服务被终止的情况下重新启动您的服务。

我在我的应用程序“自动电子邮件图片”中使用了这段代码 https://play.google.com/store/apps/details?id=com.alexpap.EmailPicturesFree

以下是CommonReceiver类。

public class CommonReceiver extends BroadcastReceiver {

    public void onReceive(Context paramContext, Intent paramIntent)
    {
        paramContext.startService(new Intent(paramContext, FileObserverService.class));
    }
}

在 AndroidManifest.xml 文件中,在应用程序的闭合标签之前,这是它的定义。
<receiver android:name="com.alexpap.services.CommonReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.USER_PRESENT"/>
    </intent-filter>
</receiver>

在StartServicesActivity活动中启动服务。

Intent iFileObserver = new Intent(StartServicesActivity.this, FileObserverService.class);
StartServicesActivity.this.startService(iFileObserver);

这里是服务 onStartCommand() 方法的示例:


public int onStartCommand(Intent intent, int flags,  int startId) {

    int res = super.onStartCommand(intent, flags, startId);

    /*** Put your code here ***/

    startServiceForeground(intent, flags, startId);

    return Service.START_STICKY;  
}

public int startServiceForeground(Intent intent, int flags, int startId) {

    Intent notificationIntent = new Intent(this, StartServicesActivity.class); 
    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

    Notification notification = new NotificationCompat.Builder(this)
        .setContentTitle("File Observer Service")
        .setContentIntent(pendingIntent)
        .setOngoing(true)
            .build();

    startForeground(300, notification);

    return START_STICKY;
}

我使用了任务管理器应用程序测试了这段代码,每次服务被杀死后,它几乎立即重新启动(执行onStartCommand())。每次打开手机和重启后也会重新启动。 我将此代码用于我的应用程序中,该应用程序会将您拍摄的每张照片发送到预定义的电子邮件列表中。发送电子邮件和接收电子邮件列表在另一个活动中设置,并存储在共享首选项中。我在几个小时内拍摄了约100张照片,并且所有照片都已成功发送到接收电子邮件。


1
无法在联想K3 Note上工作。问题是从启动器杀死应用程序时,不会调用onTaskRemoved / onDestroy。而且,一旦应用程序被杀死,广播接收器也不会为上述事件过滤器(boot_complete/connectivity_change等)调用。在这些情况下最好的做法是像@Dheeraj提到的那样启动前台服务。 - blueether

3
@Override
public void onDestroy() {

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

}

在你的服务中编写以上代码,你的服务将永远不会停止,即使用户想要销毁它或者想要杀死它,它也不会被杀死,除非你的应用程序从设备中卸载。

1
@Basher51,不是的;根据我的经验,在服务被杀死时并不总是调用onDestroy - Sam
1
@Sam:您能否分享一些您观察到onDestroy未被调用的特定情况? - Basher51
4
  1. 当应用程序升级时;
  2. 当操作系统因内存不足而终止它时;
  3. 当应用程序崩溃时。
- Sam
1
在我看来,这段代码会让事情变得更糟。当操作系统杀死你的服务时,它不起作用。当用户杀死你的服务时,它确实起作用,我认为这是一件坏事,因为它夺走了用户的控制权。此外,当你尝试使用stopService在你的代码中停止服务时,这会阻止它工作,我认为这也是一件坏事。 - Sam
1
这个很好用,正是我所寻找的,并且比其他答案简单得多,谢谢。即使我杀掉它,它也会再次启动 :) - Shahbaz Talpur
显示剩余2条评论

2

您可以尝试重复启动服务,例如每5秒钟一次。 这样,当您的服务正在运行时,它将每5秒钟执行一次onStartCommand()。我测试过此方案非常可靠,但不幸的是会稍微增加手机负担。 以下是您在活动中启动服务的代码。

Intent iFileObserver = new Intent(StartServicesActivity.this, FileObserverService.class);
PendingIntent pendingIntentFileObserver = PendingIntent.getService(StartServicesActivity.this, 0, iFileObserver, 0);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
Date now = new Date();

    //start every 5 seconds
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, now.getTime(), 5*1000, pendingIntentFileObserver);

以下是服务的 onStartCommand() 方法。

//class variable
public static boolean isStarted = false;

public int onStartCommand(Intent intent, int flags,  int startId) {

    int res = super.onStartCommand(intent, flags, startId);

    //check if your service is already started
    if (isStarted){      //yes - do nothing
        return Service.START_STICKY;
    } else {             //no 
        isStarted = true;
    }


    /**** the rest of your code  ***/

    return Service.START_STICKY;

}

0

首先在另一个进程中创建服务,然后编写广播器,在时间间隔内递归运行。

protected CountDownTimer rebootService = new CountDownTimer(9000, 9000) {


    @Override
    public void onTick(long millisUntilFinished) {


    }

    @Override
    public void onFinish() {

        sendBroadcast(reboot);
        this.start();
        Log.d(TAG, "rebootService sending PREVENT AUTOREBOT broadcast");


    }

};

然后在主进程中注册广播接收器,并使用定时器递归,该定时器在服务的第一次广播到达后启动

protected static class ServiceAutoRebooter extends BroadcastReceiver {

    private static   ServiceAutoRebooter instance = null;
    private  RebootTimer rebootTimer = null;

    private static ServiceAutoRebooter getInstance() {

        if (instance == null) {

            instance = new ServiceAutoRebooter();

        }
        return instance;

    }





    public class RebootTimer extends CountDownTimer {

        private Context _context;
        private Intent _service;


        public RebootTimer(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }


        @Override
        public void onTick(long millisUntilFinished) {

        }


        @Override
        public void onFinish() {

            _context.startService(_service);
            this.cancel();
            Log.d(TAG, "Service AutoRebooted");


        }


    }


    @Override
    public void onReceive(Context context, Intent intent) {

        if (rebootTimer == null) {
            Log.d(TAG, "rebootTimer == null");

            rebootTimer = new RebootTimer(10000, 10000);
            rebootTimer._context = context;

            Intent service = new Intent(context, SomeService.class);
            rebootTimer._service = service;
            rebootTimer.start();

        } else {

            rebootTimer.cancel();
            rebootTimer.start();
            Log.d(TAG, "rebootTimer is restarted");

        }


    }


}

如果在重启计时器(主进程)到期时未收到服务的“PREVENT AUTOREBOT”广播,服务将自动重新启动。


-3

我找到了一个解决方案...虽然回答晚了,但我还是想回答...

我们可以在服务的onDestroy中发送广播,并创建一个接收器来接收广播并重新启动服务...当它因任何原因被销毁时...


这并不是真的;根据我的经验,它似乎只涵盖了用户或开发人员停止服务的情况。 - Sam

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