导致崩溃的后台服务

Background services causing crash

我的问题可能是对服务及其使用的明显误解,或者可能与其他应用程序发生冲突。当我启动一个特定的 activity 时,我会启动两个后台服务 - 提供行驶距离的位置跟踪和经过的计时器,两者都通过 BroadcastReceiver 传递给 activity。我通过来自主 Activity:

Intent 对象使用 Long 启动每项服务
if (!Utils.isServiceRunning(this, TrackService.class)) {
    Intent i = new Intent(this, TrackService.class);
    i.putExtra("SUB_ID", submissionID);
    startService(i);
}

并且我使用下面的代码检测服务是否已经运行:

public static boolean isServiceRunning(Activity activity, Class<?> serviceClass) {
    ActivityManager manager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
    for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
        if (serviceClass.getName().equals(service.service.getClassName())) {
            return true;
        }
    }
    return false;
}

onStartCommand 的示例如下:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    submissionID = intent.getLongExtra("SUB_ID", 0L);
    // I then use this submissionID to get other data

    super.onStartCommand(intent, flags, startId);
    return START_STICKY;
}

在某些设备上,这似乎完美运行 - 服务在后台运行,允许我使用应用程序中的其他活动,还可以在我下次打开我的特定应用程序时进出应用程序activity,距离和时间更新正确。

但是,我收到了很多崩溃报告,并且来自用户的轶事证据表明它是在退出我的应用程序后使用相机(我还没有确定我的应用程序是否在使用相机时发生崩溃其他应用程序,或者当他们重新进入我的应用程序时):

Fatal Exception: java.lang.RuntimeException: Unable to start service edu.cornell.birds.ebird.services.TrackService@3f77db78 with null: java.lang.NullPointerException: Attempt to invoke virtual method 'long android.content.Intent.getLongExtra(java.lang.String, long)' on a null object reference
   at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3149)
   at android.app.ActivityThread.access00(ActivityThread.java:165)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1515)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5669)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'long android.content.Intent.getLongExtra(java.lang.String, long)' on a null object reference
   at edu.cornell.birds.ebird.services.TrackService.onStartCommand(TrackService.java:102)
   at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3112)
   at android.app.ActivityThread.access00(ActivityThread.java:165)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1515)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5669)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

在我看来,由于某种原因服务正在停止,并且在重新启动时无法访问意图中的数据(我了解 NullPointer 错误是什么)。这是因为我在重启时返回 START_STICKY 哪个 returns 空数据?

有谁知道应该停止服务的任何原因吗?我该如何防止这种情况发生?

我无法使用我自己的设备(Moto G4,Android 7.0)重新创建,它的工作方式与我希望的一样。

此问题的快速修复是:

Return START_REDELIVER_INTENT 在服务的 onStartCommand() 回调中而不是 START_STICKY 以便在重启后发送整个意图。

您可以使用如下 if 条件将代码包装在 onStartCommand 中:

if (null == intent || null == intent.getAction ()) {


        return START_STICKY;
}else{
  //ur code goes in

   return START_STICKY;
 }

这个空指针异常的原因是: "if there are not any pending start commands to be delivered to the service, it will be called with a null intent object, so you must take care to check for this."

Android 中的任何内容都可以随时终止,例如当系统资源不足时。所以你的代码应该随时准备好。后台服务的处理方式将在 O 中发生重大变化。 鉴于此,我认为您可以阅读 Android documentation:

"If this service is not already running, it will be instantiated and started (creating a process for it if needed); if it is running then it remains running. Every call to this method will result in a corresponding call to the target service's onStartCommand(Intent, int, int) method, with the intent given here."

所以不要把所有的责任都推给来电者,而是推给被调用者。每当您需要向它传递一些信息时启动该服务,然后让该服务(它已经知道它是否已实例化并且运行)来处理这种情况。另外,一如既往地防止空值 Android:

// caller, no if-check here (no responsibility)
startService(new Intent(this, TrackService.class).putExtra("SUB_ID", submissionID));

然后对方,被调用的服务:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent == null) {
        // do nothing and return
        return START_STICKY;
    }

    // here your intent is not null, use it
    submissionID = intent.getLongExtra("SUB_ID", 0L);
    // if you got the most recent data already, do nothing
    // else, retrieve data using the passed id

    return START_STICKY;

最后但同样重要的是,请注意您无需调用 super.onStartCommand(intent, flags, startId); ,查看其实现。 请记住责任倒置,因为这是一种更通用的方法,您可以使用相当多的编码 Android,不仅在本示例中。希望对您有所帮助!

Android 可以(并且将会)随时停止您的 Service。因为你已经从 onStartCommand() 返回 START_STICKY,Android 应该在你的 Service 被杀死后重新启动它。在这种情况下,您将在重新启动后在 onStartCommand() 中得到一个空的 Intent。没有办法阻止 Android 杀死你的 Service 如果它想要的话。

您需要定期将任何有用的数据保存在持久性存储(SharedPreferences、文件、数据库等)中,以便您可以在重启后恢复。

您是否在清单文件的 Intent 操作中注册了您的广播接收器?或 dynamically?