Android:保持后台服务运行(防止进程死亡)

53

我有一个被定义为:

public class SleepAccelerometerService extends Service implements SensorEventListener

我正在开发一个应用程序,可以在用户睡觉时使用手机/设备监测加速度活动,出于各种原因。这是一项长期运行的服务,在夜间绝不能被关闭。由于在夜间会发生许多后台应用程序和定期进程,因此安卓有时会关闭我的进程,从而结束我的服务。例如:

10-04 03:27:41.673: INFO/ActivityManager(1269): Process com.androsz.electricsleep (pid 16223) has died.
10-04 03:27:41.681: INFO/WindowManager(1269): WIN DEATH: Window{45509f98 com.androsz.electricsleep/com.androsz.electricsleep.ui.SleepActivity paused=false}

我不想强制用户在我的应用中使用“SleepActivity”或其他活动作为前台。 我不能让我的服务定期运行,因为它会不断拦截onSensorChanged。

有什么提示吗?源代码在这里:http://code.google.com/p/electricsleep/

7个回答

72
对于Android 2.0或更高版本,您可以使用startForeground()方法将服务启动到前台。文档说明如下

已启动的服务可以使用startForeground(int, Notification) API将服务置于前台状态,系统认为它是用户正在积极关注的内容,因此在内存不足时不会被终止。 (在当前前台应用程序的极端内存压力下,理论上仍然可能杀死服务,但实际上这不应该成为问题。)

主要是为了在终止服务会对用户造成干扰时使用,例如终止音乐播放器服务将停止音乐播放。
您需要向该方法提供一个Notification,该通知将显示在“正在进行”部分的通知栏中。

2
谢谢。我之前听说过startForeground,但我没想到它会是Service中的一个方法(不过现在想想也很合理)。 - Jon Willis
4
我有一个问题。如果我不想显示任何通知,但仍想使用startForeground()方法,该怎么办? - Farhana Haque
4
@farhanahaque - 你不能这样做。只有在关闭服务会对用户产生干扰时,例如播放音乐等情况下,才应该使用 startForeground()。因此,用户必须通过通知可见该情况。startForeground()并不意味着应该替代适当响应生命周期事件的方法。 - David Webb
1
@gonzobrains:在onStart命令中,您可以设置一个闹钟,以确保您的服务在被杀死时重新启动。以下是代码:PendingIntent localPendingIntent = PendingIntent.getService(this, 0, new Intent(this, UsbService.class), 0); AlarmManager localAlarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); Calendar localCalendar = Calendar.getInstance(); localCalendar.setTimeInMillis(System.currentTimeMillis()); localCalendar.add(13, 50); localAlarmManager.set(0, localCalendar.getTimeInMillis(), localPendingIntent); - Basher51
我想让两个服务在前台运行。我该怎么做?http://stackoverflow.com/questions/35168209/are-their-any-negatives-impacts-of-launching-two-continues-services-at-the-same - Ruchir Baronia
显示剩余2条评论

17
当你使用BIND_AUTO_CREATE将服务绑定到活动时,你的服务会在活动销毁和解绑后立即被销毁。这与你如何实现服务的解绑方法无关,它仍然会被销毁。
另一种方法是从活动中使用startService方法启动服务。这样,即使活动被销毁,你的服务也不会被销毁或暂停,但你需要在适当的时候使用stopSelf/stopService来暂停/销毁它。
更新:自从Android API 26左右,我们需要使用startForeground方法,否则服务将会停止,并显示类似于日志的信息。
W/ActivityManager: Stopping service due to app idle: u0a577 -1m20s400ms com.example.package/com.example.MyServiceClass

12
如已经由Dave指出,您可以以前台优先级运行您的Service。但是仅在绝对必要时才应使用此方法,即当如果Service被Android杀死会导致不良用户体验时。这就是“前台”的真正含义:您的应用程序以某种方式在前台,如果它被杀死,用户将立即注意到它(例如因为它正在播放歌曲或视频)。
在大多数情况下,请求将您的Service设置为前台优先级是适得其反的!
为什么呢?当Android决定杀死一个Service时,它会这样做是因为它短缺资源(通常是RAM)。根据不同的优先级类别,Android决定终止哪些正在运行的进程,包括服务,以释放资源。这是您希望发生的健康流程,以使用户拥有流畅的体验。如果您没有充分的理由请求前台优先级,只是为了防止您的Service被杀死,那么它很可能会导致不良用户体验。或者您能保证您的Service始终保持最小的资源消耗并且没有内存泄漏吗?
Android提供了粘性服务(sticky services)来标记那些在被杀死后应该在一段宽限期之后重新启动的服务。这个重新启动通常会在几秒钟内发生。
假设您想为Android编写XMPP客户端。在包含XMPP连接的Service中请求前台优先级是否合适?绝对不,没有任何理由这样做。但是您想将START_STICKY用作服务的onStartCommand方法的返回标志。这样,在有资源压力时停止您的服务,并在情况恢复正常时重新启动它。 1: 我非常确定许多Android应用程序存在内存泄漏。这是业余(桌面)程序员不太关心的事情。

1
START_STICKY并不是用来实现这个功能的,而是为了确保在进程被杀掉后,一个有时间限制的任务能够完成。如果由于某种原因服务无法“粘性连接”(例如无法重新连接到XMPP服务器),系统可能会认为尝试次数已经足够多,并停止重新启动服务,更不用说这还需要在连接正常时将服务保持在循环中运行,这是不可能做到的。话虽如此,我找不到在进程被杀掉后保持/恢复连接的解决方案(除了通过AlarmManager定期进行重新连接尝试)。 - Piovezan
4
“START_STICKY” 正是你在这里需要使用的。 “...尝试了足够多的次数并且停止重新启动服务,…” 不,Android系统不知道XMPP连接(重新)尝试所执行的粘性Android服务。 它只看到服务正在运行,并使其保持运行状态。 这是Android API提供的契约,如果服务被粘性地启动。 “...提到这将需要使服务在循环中保持运行…” 是的,这正是为什么(大多数)Android服务都有一个Looper的原因(通常使用Android服务抽象化用户)。 - Flow
抱歉,我从START_REDELIVER_INTENT中误解了它。虽然服务肯定有一个Looper来处理传递给它的意图队列,但在持久连接的情况下,我们不应该保持它以这种方式运行,而是在Service.onStartCommand()IntentService.onHandleIntent()方法中传递单个意图并保持while循环,这是不可行的。 - Piovezan
“将while循环放在内部”。“你为什么认为你需要这样的while循环?你不需要它。” - Flow
2
我再次查看了文档,我认为你是正确的。我误解了Android Service的概念。一直在想为什么需要stopSelf()方法...原来,如果服务没有停止,空闲服务的Loopers对系统来说相当便宜,我只是没想到它们会被设计成这样。感谢您的澄清! - Piovezan
显示剩余3条评论

5
我遇到了类似的问题。在一些设备上,安卓系统会在一段时间后杀死我的服务,即使使用 startForeground() 也无法解决。而且我的客户不喜欢这个问题。我的解决方案是使用 AlarmManager 类来确保服务在必要时运行。我使用 AlarmManager 创建了一个看门狗定时器。它会定期检查服务是否应该运行并重新启动服务。 此外,我使用 SharedPreferences 来保存服务是否应该运行的标志。
创建/取消我的看门狗定时器:
void setServiceWatchdogTimer(boolean set, int timeout)
{
    Intent intent;
    PendingIntent alarmIntent;
    intent = new Intent(); // forms and creates appropriate Intent and pass it to AlarmManager
    intent.setAction(ACTION_WATCHDOG_OF_SERVICE);
    intent.setClass(this, WatchDogServiceReceiver.class);
    alarmIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    AlarmManager am=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
    if(set)
        am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, alarmIntent);
    else
        am.cancel(alarmIntent);
}

收到并处理看门狗计时器的意图:
/** this class processes the intent and
 *  checks whether the service should be running
 */
public static class WatchDogServiceReceiver extends BroadcastReceiver
{
    @Override
    public void onReceive(Context context, Intent intent)
    {

        if(intent.getAction().equals(ACTION_WATCHDOG_OF_SERVICE))
        {
            // check your flag and 
            // restart your service if it's necessary
            setServiceWatchdogTimer(true, 60000*5); // restart the watchdogtimer
        }
    }
}

实际上我使用WakefulBroadcastReceiver而不是BroadcastReceiver。我给你的代码中使用BroadcastReceiver只是为了简化它。


4

http://developer.android.com/reference/android/content/Context.html#BIND_ABOVE_CLIENT

BIND_ABOVE_CLIENT 是一个常量,API level 14 中新增。

bindService(Intent, ServiceConnection, int) 中使用的标志:表示绑定到该服务的客户端应用程序认为此服务比应用本身更重要。当设置时,平台会尝试在杀死绑定的服务之前杀死应用程序,但这并不能保证一定成功。

同组的其他标志包括:BIND_ADJUST_WITH_ACTIVITY、BIND_AUTO_CREATE、BIND_IMPORTANT、BIND_NOT_FOREGROUND 和 BIND_WAIVE_PRIORITY。

请注意,BIND_AUTO_CREATE 的含义在 ICS 中已更改,而不指定 BIND_AUTO_CREATE 的旧应用程序会自动将标志 BIND_WAIVE_PRIORITYBIND_ADJUST_WITH_ACTIVITY 设置为它们。


2

我正在开发一个应用程序,在遇到应用程序停止运行的问题时,我的服务也会被终止。我在谷歌上进行了调研,发现需要将其设置为前台服务。以下是代码:

public class UpdateLocationAndPrayerTimes extends Service {

 Context context;
@Override
public void onCreate() {
    super.onCreate();
    context = this;
}

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

    StartForground();
    return START_STICKY;
}

@Override
public void onDestroy() {


    super.onDestroy();
}

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


private void StartForground() {
    LocationChangeDetector locationChangeDetector = new LocationChangeDetector(context);
    locationChangeDetector.getLatAndLong();
    Notification notification = new NotificationCompat.Builder(this)
            .setOngoing(false)
            .setSmallIcon(android.R.color.transparent)

            //.setSmallIcon(R.drawable.picture)
            .build();
    startForeground(101,  notification);

    }
}

希望能对您有所帮助!

2

保持服务的占用尽量小,这样可以降低Android关闭你的应用程序的概率。你无法完全防止应用被杀死,因为如果可以完全防止,则会轻易地创建出具有持续性的间谍软件。


1
或者您可以启动另一个服务,让它们相互监视,以确保如果其中一个被杀死,则另一个会重新启动它。虽然不太美观,但可能是有效的。 - BeRecursive
2
我确实需要优化我的服务所消耗的内存(我将把数据转储到SQLite文件数据库中),特别是因为它随着时间的推移而不断增长,但是,我想知道为什么我的服务被杀死,而其他服务继续运行。我知道服务中有一个优先级系统-我该如何提高我的服务优先级? - Jon Willis
1
@Jon - 如果你的服务被终止,它最终会重新启动。我知道你说过你不想这样做,但唯一提高优先级的方法是让操作系统相信该服务当前对用户处于活动状态(在前台使用startForeground(int, Notification))。 - BeRecursive
9
“保持应用程序小并不会降低其被杀死(暂时性的直到重启)的概率”,这是不正确的说法。虽然在不同版本中具体使用的算法有所变化,但通常情况下,该算法会确保后台服务偶尔会被杀死/重启,而不考虑它们使用的内存大小。 - hackbod
我想让两个服务在前台运行。我该怎么做?http://stackoverflow.com/questions/35168209/are-their-any-negatives-impacts-of-launching-two-continues-services-at-the-same - Ruchir Baronia

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