在高端手机上杀死了最小的 android 前台服务 [英] Minimal android foreground service killed on high-end phone

查看:53
本文介绍了在高端手机上杀死了最小的 android 前台服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个应用程序,让用户可以记录路线(位置/GPS).为了确保即使在屏幕关闭时也能记录位置,我为位置记录创建了一个 前台服务.我将位置存储在 Room Database 中,该数据库使用 Dagger2 注入到我的服务中.

然而,这个服务被Android杀死了,这当然不好.我可以订阅内存不足警告,但这并不能解决我的服务在运行 Android 8.0 的现代高端手机上大约 30 分钟后被终止的潜在问题

我创建了一个只有Hello world"活动和服务的最小项目:

使用adb shell dumpsys活动进程>tmp.txt 我可以确认 foregroundServices=true 并且我的服务在 LRU 列表中排在第 8 位:

Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)

似乎不可能创建一个您可以信任不会被杀死的前台服务.所以,我们能做些什么?嗯...

  1. 将服务放在一个单独的进程中,试图让 Android 终止 UI/活动,同时不理会服务.可能会有所帮助,但似乎不能保证
  2. 在服务中坚持一切,例如房间数据库.每个变量,每个自定义类,每次发生任何更改,然后使用 START_STICKY 启动服务.这看起来有点浪费并且不会产生非常漂亮的代码,但它可能会起作用......有点.根据 Android 杀死服务后重新创建服务所需的时间,可能会丢失很大一部分位置.

这真的是在 Android 后台做事的当前状态吗?没有更好的方法吗?

将应用列入白名单以优化电池(禁用它)并不会阻止我的服务被终止

使用 Context.startForegroundService() 启动服务并没有改善这种情况

所以这确实只发生在某些设备上,但它始终发生在它们上.我想您必须做出选择,要么不支持大量用户,要么编写非常丑陋的代码.太棒了.

解决方案

startForeground 启动的服务属于第二个最重要的组 可见进程:

<块引用>

  1. 一个可见的进程正在做用户当前知道的工作,所以杀死它会对用户产生明显的负面影响经验.一个进程在下面被认为是可见的条件:

    1. 它正在运行一个用户在屏幕上可见但在前台不可见的 Activity(其 onPause() 方法已被调用).这个例如,如果前台活动显示为允许在其后面看到前一个 Activity 的对话框.

    2. 它有一个作为前台服务运行的 Service,通过 Service.startForeground()(它要求系统处理服务作为用户知道或基本上可见的东西他们).

    3. 它托管了一项服务,系统将其用于用户知道的特定功能,例如动态壁纸、输入法服务等

    系统中运行的这些进程的数量是有限的与前台进程相比,但仍然相对受控.这些进程被认为是极其重要的,不会被杀死除非需要这样做以保持所有前台进程运行.

话虽如此,您永远无法确定您的服务不会在任何时候被终止.例如.内存压力、电池电量不足等.请参阅 谁生谁死.

<小时>

关于如何处理,基本上你自己回答了这个问题.要走的路是START_STICKY:

<块引用>

对于已启动的服务,还有两种主要的模式他们可以决定运行的操作,这取决于他们的价值从 onStartCommand() 返回: START_STICKY 用于服务根据需要明确启动和停止,而 START_NOT_STICKYSTART_REDELIVER_INTENT 用于仅应在处理发送给他们的任何命令时保持运行.见有关语义的更多详细信息,请链接文档.

作为一般准则,您应该在后台(矿石前台)服务中尽可能少做,即只进行位置跟踪,而将其他所有内容保留在前台活动中.只有跟踪应该需要很少的配置并且可以快速加载.此外,您的服务越小,它被杀死的可能性就越小.您的活动将被系统恢复到进入后台之前的状态,只要它没有被杀死.另一方面,前台活动的冷启动"应该不成问题.
我认为这并不难看,因为这可以保证手机始终为用户提供最佳体验.这是它必须做的最重要的事情.不幸的是,有些设备会在 30 分钟后(可能没有用户交互)关闭服务.

所以,正如你所说,你必须

<块引用>

保留服务中的所有内容,例如房间数据库.每一个变量,每个自定义类,每次它们中的任何一个发生变化,然后使用 START_STICKY 启动服务.

参见创建永无止境的服务

隐含问题:

<块引用>

取决于Android重新创建所需的时间杀死后服务,可能会丢失很大一部分位置.

这通常只需要很短的时间.特别是因为您可以使用 Fused Location Provider Api 进行位置更新,这是一个独立的系统服务,极不可能被杀死.所以主要看你需要在onStartCommand中重新创建服务的时间.

<块引用>

另请注意,从 Android 8.0 开始,您需要使用forground service 因为背景位置限制.

<小时>

正如最近在新闻中报道的那样:一些制造商可能会让您很难保持服务正常运行.网站 https://dontkillmyapp.com/ 会跟踪制造商和您设备的可能缓解措施.Oneplus 目前 (29.01.19) 是最严重的违规者之一.

<块引用>

在发布 1+5 和 1+6 手机时,OnePlus 推出了其中一款迄今为止市场上最严格的背景限制,甚至相形见绌由小米或华为执行的那些.用户不仅需要启用额外的设置以使他们的应用程序正常运行,但这些设置甚至可以通过固件更新进行重置,以便应用程序和用户再次崩溃需要定期重新启用这些设置.

用户解决方案

关闭系统设置 > 应用程序 > 齿轮图标 > 特殊访问 > 电池优化.

遗憾的是

<块引用>

开发者端没有已知的解决方案

I'm trying to create an app that lets users log routes (locations/GPS). To ensure locations are logged even when the screen is off, I have created a foreground service for the location logging. I store the locations in a Room Database which is injected into my service using Dagger2.

However, this service is killed by Android which is, of course, not good. I could subscribe to low memory warnings but that doesn't solve the underlying problem of my service getting killed after ~30 minutes on a modern high-end phone running Android 8.0

I have created a minimal project with only a "Hello world" activity and the service: https://github.com/RandomStuffAndCode/AndroidForegroundService

The service is started in my Application class, and route logging is started through a Binder:

// Application
@Override
public void onCreate() {
    super.onCreate();
    mComponent = DaggerAppComponent.builder()
            .appModule(new AppModule(this))
            .build();

    Intent startBackgroundIntent = new Intent();
    startBackgroundIntent.setClass(this, LocationService.class);
    startService(startBackgroundIntent);
}

// Binding activity
bindService(new Intent(this, LocationService.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
// mConnection starts the route logging through `Binder` once connected. The binder calls startForeground()

I probably don't need the BIND_AUTO_CREATE flag, I've been testing different flags in an attempt to not get my service killed - no luck so far.

Using the profiler it does not seem like I have any memory leaks, memory usage is stable at ~35mb:

Using adb shell dumpsys activity processes > tmp.txt i can confirm that foregroundServices=true and my service is listed 8th in the LRU list:

Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)

It seems like it is not possible to create a foreground service that you can trust to not get killed. So what can we do? Well...

  1. Put the service in a separate process, in an attempt to let Android kill the UI/Activities while leaving the service alone. Would probably help, but doesn't seem like a guarantee
  2. Persist everything in the service in e.g. a Room database. Every variable, every custom class, every time any of the changes and then start the service with START_STICKY. This seems kind of wasteful and doesn't lead to very beautiful code, but it would probably work... somewhat. Depending on how long it takes for Android to re-create the service after killing it, a large portion of locations may be lost.

Is this really the current state of doing stuff in the background on Android? Isn't there a better way?

EDIT: Whitelisting the app for battery optimization (disabling it) does not stop my service from being killed

EDIT: Using Context.startForegroundService() to start the service does not improve the situation

EDIT: So this indeed only occurs on some devices, but it occurs consistently on them. I guess you have to make a choice of either not supporting a huge number of users or write really ugly code. Awesome.

解决方案

A service started by startForeground besongs to the second most important group visible process:

  1. A visible process is doing work that the user is currently aware of, so killing it would have a noticeable negative impact on the user experience. A process is considered visible in the following conditions:

    1. It is running an Activity that is visible to the user on-screen but not in the foreground (its onPause() method has been called). This may occur, for example, if the foreground Activity is displayed as a dialog that allows the previous Activity to be seen behind it.

    2. It has a Service that is running as a foreground service, through Service.startForeground() (which is asking the system to treat the service as something the user is aware of, or essentially visible to them).

    3. It is hosting a service that the system is using for a particular feature that the user is aware, such as a live wallpaper, input method service, etc.

    The number of these processes running in the system is less bounded than foreground processes, but still relatively controlled. These processes are considered extremely important and will not be killed unless doing so is required to keep all foreground processes running.

That being said, you can never be sure that your service is not killed at any time. E.g. memory pressure, low battery etc. See who-lives-and-who-dies.


For how to handle it, basically you answered the question yourself. The way to go is START_STICKY:

For started services, there are two additional major modes of operation they can decide to run in, depending on the value they return from onStartCommand(): START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them. See the linked documentation for more detail on the semantics.

As a general guideline you should do as little as possible in the background (ore foreground) service, i.e. only do the location tracking and keep everything else in your foreground activity. Only the tracking should require very little configuration an can be loaded quickly. Also the smaller your service is the less likely it is to be killed. Your activity will be restored by the system in the state that is was before it went into background, as long as it is not killed as well. A "cold-start" of the foreground activity on the other hand should not be a problem.
I don't consider that as ugly, because this guarantees that the phone always provides the best experience to the user. This is the most important thing it has to do. That some devices close services after 30 minutes (possibly without user interaction) is unfortunate.

So, as you stated, you have to

Persist everything in the service in e.g. a Room database. Every variable, every custom class, every time any of them changes and then start the service with START_STICKY.

See creating a never ending service

Implicit question:

Depending on how long it takes for Android to re-create the service after killing it, a large portion of locations may be lost.

This usually takes only a really short time. Especially because you can use the Fused Location Provider Api for the location updates, which is an independent system service and very unlikely to be killed. So it mainly depends on the time you need to recreate the service in onStartCommand.

Also take note that from Android 8.0 onwards you need to use a forground service because of the background location limits.


Edit: As recently covered in the news: Some manufacturers may give you a hard time to keep your service running. The site https://dontkillmyapp.com/ keeps track of the manufacturers and possible mitigations for your device. Oneplus is currently (29.01.19) one of the worst offenders.

When releasing their 1+5 and 1+6 phones, OnePlus introduced one of the most severe background limits on the market to date, dwarfing even those performed by Xiaomi or Huawei. Not only did users need to enable extra settings to make their apps work properly, but those settings even get reset with firmware update so that apps break again and users are required to re-enable those settings on a regular basis.

Solution for users

Turn off System Settings > Apps > Gear Icon > Special Access > Battery Optimization.

sadly there is

No known solution on the developer end

这篇关于在高端手机上杀死了最小的 android 前台服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆