最小的Android前台服务在高端手机上被杀死

问题描述 投票:19回答:3

我正在尝试创建一个允许用户记录路线(位置/ GPS)的应用程序。为了确保即使屏幕关闭也记录了位置,我已经为位置记录创建了一个foreground service。我将位置存储在Room Database中,使用Dagger2将其注入我的服务中。

但是,这项服务被Android杀死,当然这并不好。我可以订阅低内存警告,但这并没有解决我的服务在运行Android 8.0的现代高端手机上大约30分钟后被杀的根本问题

我创建了一个只有“Hello world”活动和服务的最小项目:https://github.com/RandomStuffAndCode/AndroidForegroundService

该服务在我的Application类中启动,路由记录通过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()

我可能不需要BIND_AUTO_CREATE标志,我一直在测试不同的标志,试图不让我的服务被杀 - 到目前为止没有运气。

使用分析器看起来我没有任何内存泄漏,内存使用率稳定在~35mb:

profiler

使用adb shell dumpsys activity processes > tmp.txt我可以确认foregroundServices=true和我的服务列在LRU列表中的第8位:

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

似乎无法创建一个您可以信任的前台服务,以免被杀死。所以,我们能做些什么?好...

  1. 将服务放在一个单独的进程中,试图让Android在单独离开服务时终止UI / Activities。可能会有所帮助,但似乎不是一种保证
  2. 保留服务中的所有内容,例如房间数据库。每个变量,每个自定义类,每次任何更改,然后使用START_STICKY启动服务。这似乎有点浪费,并没有导致非常漂亮的代码,但它可能会工作......有点。根据Android杀死服务后重新创建服务所需的时间,很大一部分地点可能会丢失。

这真的是在Android背景下做事情的当前状态吗?有没有更好的方法?

编辑:将应用程序列入白名单以进行电池优化(禁用它)并不会阻止我的服务被杀死

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

编辑:所以这确实只发生在某些设备上,但它始终存在于它们上面。我想你必须选择不支持大量用户或编写非常难看的代码。真棒。

android android-service android-lifecycle android-location android-doze
3个回答
16
投票

startForeground开始的服务是第二个最重要的组织visible process

  1. 一个可见的过程正在进行用户当前意识到的工作,因此杀死它会对用户体验产生明显的负面影响。在以下条件下,可以看到一个过程: 它正在运行一个活动,该活动在屏幕上对用户可见,但不在前台(已调用其onPause()方法)。例如,如果前景活动显示为允许在其后面看到前一个活动的对话框,则可能会发生这种情况。 它有一个作为前台服务运行的服务,通过Service.startForeground()(它要求系统将服务视为用户知道或基本上对他们可见的东西)。 它正在托管系统用于用户知道的特定功能的服务,例如动态壁纸,输入法服务等。 在系统中运行的这些进程的数量比前台进程更少,但仍然是相对受控的。这些进程被认为是非常重要的,除非需要这样做以保持所有前台进程运行,否则不会被杀死。

话虽这么说,你永远不能确定你的服务在任何时候都不会被杀死。例如。记忆压力,电池电量低等。见who-lives-and-who-dies


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

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

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

所以,正如你所说,你必须这样做

保留服务中的所有内容,例如房间数据库。每个变量,每个自定义类,每次更改任何变量,然后使用START_STICKY启动服务。

creating a never ending service

隐含的问题:

根据Android杀死服务后重新创建服务所需的时间,很大一部分地点可能会丢失。

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

另请注意,从Android 8.0起,您需要使用forground service,因为background location limits


编辑:最近新闻报道:有些制造商可能会给你一个很难保持服务运行的时间。网站https://dontkillmyapp.com/会跟踪您的设备的制造商和可能的缓解措施。 Oneplus目前(29.01.19)是最严重的罪犯之一。

在推出1 + 5和1 + 6手机时,OnePlus推出了迄今为止市场上最严苛的背景限制之一,甚至使小米或华为的手机相形见绌。用户不仅需要启用额外的设置才能使他们的应用程序正常运行,而且这些设置甚至会通过固件更新重置,以便应用程序再次中断,并且用户需要定期重新启用这些设置。

用户解决方案

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

遗憾的是有

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


2
投票

我知道现在已经很晚了,但这可能对某人有所帮助。我也面临同样的问题,即保持前台服务的活跃,而不会被不同制造商的操作系统杀死。大多数中国制造商的操作系统都会杀死前台服务,即使它被添加到例外列表(电池,清洁器等)并允许自动启动。

我发现这个link解决了我长期保持服务活着的问题。

您所要做的就是在一个单独的过程中运行前台服务。而已。

您可以通过在AndroidManifest.xml中将android:process添加到您的服务来实现。

例如:

<service android:name=".YourService"
        android:process=":yourProcessName" />

你可以参考docs了解更多有关android:process的信息

编辑:SharedPreferences不能跨多个进程工作。在这种情况下,您必须使用IPC(进程间通信)方法,或者您可以使用ContentProviders存储和访问要跨进程使用的数据。来自docs


0
投票

我建议你使用这些:AlarmManagerPowerManagerWakeLockThreadWakefulBroadcastReceiverHandlerLooper

我假设你已经在使用那些“单独的过程”和其他调整。

所以在你的Application课程中:

MyApp.java

import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log; 

public final class MyApp extends Application{

public static PendingIntent pendingIntent = null;
public static Thread                infiniteRunningThread;
public static PowerManager          pm;
public static PowerManager.WakeLock wl;


@Override
public void onCreate(){
    try{
        Thread.setDefaultUncaughtExceptionHandler(
                (thread, e)->restartApp(this, "MyApp uncaughtException:", e));
    }catch(SecurityException e){
        restartApp(this, "MyApp uncaughtException SecurityException", e);
        e.printStackTrace();
    }
    pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    if(pm != null){
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
        wl.acquire(10 * 60 * 1000L /*10 minutes*/);
    }

    infiniteRunningThread = new Thread();

    super.onCreate();
}

public static void restartApp(Context ctx, String callerName, Throwable e){
    Log.w("TAG", "restartApp called from " + callerName);
    wl.release();
    if(pendingIntent == null){
        pendingIntent =
                PendingIntent.getActivity(ctx, 0,
                                          new Intent(ctx, ActivityMain.class), 0);
    }
    AlarmManager mgr = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
    if(mgr != null){
        mgr.set(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis() + 10, pendingIntent);
    }
    if(e != null){
        e.printStackTrace();
    }
    System.exit(2);
}
}

然后在您的服务中:

ServiceTrackerTest.java

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.WakefulBroadcastReceiver;

public class ServiceTrackerTest extends Service{

private static final int SERVICE_ID = 2018;
private static PowerManager.WakeLock wl;

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

@Override
public void onCreate(){
    super.onCreate();
    try{
        Thread.setDefaultUncaughtExceptionHandler(
                (thread, e)->MyApp.restartApp(this,
                                              "called from ServiceTracker onCreate "
                                              + "uncaughtException:", e));
    }catch(SecurityException e){
        MyApp.restartApp(this,
                         "called from ServiceTracker onCreate uncaughtException "
                         + "SecurityException", e);
        e.printStackTrace();
    }
    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    if(pm != null){
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
        wl.acquire(10 * 60 * 1000L /*10 minutes*/);
    }


    Handler h = new Handler();
    h.postDelayed(()->{

        MyApp.infiniteRunningThread = new Thread(()->{
            try{
                Thread.setDefaultUncaughtExceptionHandler(
                        (thread, e)->MyApp.restartApp(this,
                                                      "called from ServiceTracker onCreate "
                                                      + "uncaughtException "
                                                      + "infiniteRunningThread:", e));
            }catch(SecurityException e){
                MyApp.restartApp(this,
                                 "called from ServiceTracker onCreate uncaughtException "
                                 + "SecurityException "
                                 + "infiniteRunningThread", e);
                e.printStackTrace();
            }

            Looper.prepare();
            infiniteRunning();
            Looper.loop();
        });
        MyApp.infiniteRunningThread.start();
    }, 5000);
}

@Override
public void onDestroy(){
    wl.release();
    MyApp.restartApp(this, "ServiceTracker onDestroy", null);
}

@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId){
    if(intent != null){
        try{
            WakefulBroadcastReceiver.completeWakefulIntent(intent);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    startForeground(SERVICE_ID, getNotificationBuilder().build());
    return START_STICKY;
}


private void infiniteRunning(){
    //do your stuff here
    Handler h = new Handler();
    h.postDelayed(this::infiniteRunning, 300000);//5 minutes interval
}

@SuppressWarnings("deprecation")
private NotificationCompat.Builder getNotificationBuilder(){
    return new NotificationCompat.Builder(this)
                   .setContentIntent(MyApp.pendingIntent)
                   .setContentText(getString(R.string.notification_text))
                   .setContentTitle(getString(R.string.app_name))
                   .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                                                              R.drawable.ic_launcher))
                   .setSmallIcon(R.drawable.ic_stat_tracking_service);
}

}

忽略“弃用”和东西,当你别无选择时使用它们。我认为代码很清楚,不需要解释。这只是解决方法的建议和解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.