在高端手机

时间:2018-04-03 19:41:30

标签: android android-service android-lifecycle android-location android-doze

我正在尝试创建一个允许用户记录路线(位置/ 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杀死服务后重新创建服务所需的时间,很大一部分地点可能会丢失。
  3. 这真的是Android在后台做事的当前状态吗?难道没有更好的方法吗?

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

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

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

3 个答案:

答案 0 :(得分:14)

startForeground besongs开始的服务到第二个最重要的群组visible process

  
      
  1. 可见的流程正在进行用户当前意识到的工作,   因此杀死它会对用户产生明显的负面影响   经验。以下将认为过程可见   条件:

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

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

    4.   
    5. 它正在托管系统用于用户知道的特定功能的服务,例如动态壁纸,输入法   服务等
    6.         

      系统中运行的这些进程数量较少   比前景进程,但仍然相对控制。的这些   过程被认为是非常重要的,不会被杀死   除非需要这样做以保持所有前台进程正常运行

        

话虽如此,你永远不能确定你的服务在任何时候都不会被杀死。例如。内存压力,电池电量不足等。请参阅who-lives-and-who-dies

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

  

对于已启动的服务,还有两种主要模式   根据他们的价值,他们可以决定参加比赛   从onStartCommand(): START_STICKY返回用于提供的服务   根据需要显式启动和停止,而 START_NOT_STICKY   或START_REDELIVER_INTENT仅用于服务   在处理发送给他们的任何命令时保持运行。见   链接文档以获取有关语义的更多详细信息。

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

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

  

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

请参阅creating a never ending service

隐含问题:

  

取决于Android重新创建的时间   杀死它后服务,很大一部分地点可能会丢失。

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

  

另请注意,从Android 8.0开始,您需要使用   由于background location limits

,因此forground service

编辑: 正如最近新闻所述: 有些制造商可能会让您难以保持服务的运行。网站https://dontkillmyapp.com/会跟踪您的设备的制造商和可能的缓解措施。 Oneplus目前(29.01.19)是最严重的罪犯之一。

  

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

     

用户解决方案

     

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

遗憾的是有

  

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

答案 1 :(得分:2)

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

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

您要做的就是在单独的过程中运行前台服务。就是这样。

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

例如:

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

您可以参考docs来进一步了解android:process

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

答案 2 :(得分: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);
}

}

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