49

我正在尝试创建一个允许用户记录路线(位置/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:

剖析器

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

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

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

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

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

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

编辑:使用Context.startForegroundService()启动服务并不能改善情况

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

4

3 回答 3

35

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

  1. 一个可见的进程正在做用户当前知道的工作,因此杀死它会对用户体验产生明显的负面影响。在以下情况下,进程被视为可见:

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

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

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

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

话虽如此,您永远无法确定您的服务在任何时候都不会被终止。例如内存压力、电池电量低等。查看谁生还谁死


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

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

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

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

将服务中的所有内容保存在例如 Room 数据库中。每个变量,每个自定义类,每次它们中的任何一个发生更改时,然后使用 START_STICKY 启动服务。

请参阅创建永无止境的服务

隐式问题:

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

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

forground service另请注意,从 Android 8.0 开始,由于后台位置限制,您需要使用 a 。


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

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

用户解决方案

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

可悲的是

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

于 2018-04-11T18:39:27.113 回答
10

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

我发现这个链接解决了我长久以来保持服务活跃的问题。

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

您可以通过android:process在 AndroidManifest.xml 中添加服务来做到这一点。

例如:

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

您可以参考文档以了解更多信息android:process

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

于 2019-03-06T11:55:02.810 回答
-1

我建议你使用这些: AlarmManager, PowerManager, WakeLock, Thread, WakefulBroadcastReceiver, Handler,Looper

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

所以在你的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);
}

}

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

于 2018-04-12T12:06:41.383 回答