保持服务在屏幕方向变化期间运行

10
在我的应用程序中,我有一个Service负责管理与外部设备的蓝牙连接。这个Service类周期性地轮询外部蓝牙设备的数据,并将最新的数据添加到缓存(或可能是SD卡)内的日志中。
在我拥有的各种Activity类中,有一个特定的Activity表示主UI。它负责以图形形式显示基于缓存文件数据的记录数据。让我们称这个ActivityDashboard。用户可以在该图表上向前和向后滚动以查看自应用程序启动以来缓存中收集和记录的数据。
针对此问题,需要考虑两种操作模式。用户可以选择“记录到SD卡”选项,即使所有Activity类都被销毁(例如,用户已返回启动器),应用程序也必须继续轮询和记录到SD卡。在这种情况下,我的Service使用.startService()启动并继续运行,只有当用户再次调用应用程序并禁用SD卡记录时,它才会停止。另一种模式是用户没有选择“记录到SD卡”,在这种情况下,Service仍在管理蓝牙连接,轮询和记录到缓存内,以便在仅使用DashboardActivity时,能够将数据可视化地显示在图表上。
目前的情况是,DashboardActivity最初使用bindService()绑定到Service,并在onPause()方法中做出相应的调用unbindService()(否则,当然会泄漏Service)。问题在于 Service 需要在方向更改或用户调用另一个Activity(例如检查电子邮件)时维护蓝牙连接并继续记录。如果用户选择“记录到SD卡”,从而调用了startService(),那么当然没有问题。问题是如何区分由于方向(或某些其他配置)更改而销毁并重新创建的Activity和因用户返回到启动器而被销毁的Activity之间的区别。对于前者,我不希望Service的数据记录中断。对于后者,在用户未选择“记录到SD卡”的情况下,我希望Service停止。
目前我能想到的最佳解决方案是始终使用startService()来启动服务,这样当Dashboard被销毁时仍会继续运行。然后,我将在Service内部实现超时机制,即Service将在连续SD卡日志记录已启用时不会停止,或者Dashboard在五秒钟内(例如)再次onCreate并重新绑定到Service。这似乎有点粗糙,我无法帮助想到这必须是一个常见的设计问题,我忽略了更好的解决方案。
4个回答

8

选项1:如果你希望在主活动结束时立即销毁服务,但不是在旋转期间销毁:

为了避免自动停止服务,您必须在绑定之前手动启动它:

protected void onStart() {
  Intent intent = new Intent(getApplicationContext(), MServcie.class);
  startService(intent);
  bindService(intent, connection, Context.BIND_AUTO_CREATE);
}

只有在您的 Activity 结束时,才停止服务!

protected void onStop() {
  if (service != null) {
    unbindService(connection);
    service = null;
    if (isFinishing()) {
      stopService(new Intent(getApplicationContext(), MyServcie.class));
    }
  }
}

选项2:如果您希望操作系统决定何时停止服务。

protected void onStart() {
    Intent intent = new Intent(this, MServcie.class);
    getApplicationContext().bindService(intent, this, Context.BIND_AUTO_CREATE);
}

我应该在onStart()和onStop()上面的代码之前还是之后包含它们的默认super调用?如果不包括super调用,似乎应用程序会崩溃。 - ecle

4
你应该使用isChangingConfigurations()而不是isFinishing(),后者在活动失去可见性和配置更改时都会返回false,因此您无法区分二者。

4
您可以采用的一种方法是,在onPause()中检查Activity是否正在结束,如果正在结束,则将解绑推迟到onDestroy()方法中。然而,在调用onDestroy()之前,您可以在onRetainNonConfigurationInstance()方法中保存您的ServiceConnection。如果您执行了该持久化操作,则根本不需要在onDestroy()中调用unBind(),只需让新实例的Activity执行解绑即可。
话虽如此,根据您的应用程序,您可能会发现启动/停止服务更容易些。

非常感谢您,Justin。我之前没有意识到isFinishing()方法(这表明我应该再次查看Activity文档)。现在,使用此方法的结果,如果我知道Activity正在被销毁但不是完成状态,我将调用startService() - Trevor
只要确保您正确阅读文档。当您的 Activity 将要被销毁时,IsFinishing 应该始终为 true。至少这是我理解的。 - Justin Breitfeller
这样做不会导致运行时错误吗?因为你正在泄漏连接。 - Sam
1
@Sam 这个答案已经过时且部分内容有误导性。Frido 提供的 Option 1 是一个更好的、布局更合理的答案,包含了所有必要的步骤。 - Justin Breitfeller
@JustinBreitfeller 谢谢您的回复。如果您使用此选项,那么您是否失去了绑定服务的某些优势(即您可以从多个应用程序组件绑定到它)?如果我从正在运行后台查询的某个对象以及从活动中绑定到它以进行一些前端UI IO,该怎么办? - Sam
显示剩余2条评论

3
您可能已经知道,服务是为了与活动具有独立的生命周期而存在的。因此,在纯监控场景下,您可以考虑不使用服务。在后台情况下,您只需要一个与活动不相关的生命周期,但在纯监控情况下则不需要。
实际上,您似乎想要将日志记录绑定到应用程序的生命周期,并通过启动/停止活动或服务来控制应用程序的生命周期。如果您在可从应用程序对象访问的单独线程中进行轮询,则您的活动根本不需要绑定服务,而只需与此日志记录线程通信即可。当您需要后台记录日志时,您可以启动/停止服务,该服务也会与记录线程通信以在停止时进行优雅的清理,正确地重新启动等。

非常感谢您的回答。我已经接受了Justin关于使用onFinishing()的建议,因为那解决了问题,但是您关于将轮询线程放在应用程序对象中的建议是有趣的,我可能会尝试一下。我已经有一个扩展Application的对象,用于保存少量全局状态的目的。 - Trevor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接