何时使用以及何时不使用Android中的服务

28

我已经从事Android开发接近两年,但这个看似简单的问题我仍感困惑。

何时应该实现服务?从我的经验来看,虽然有一些罕见的情况需要使用服务,但我对此持怀疑态度,因为在每台手机上都运行着相当多的服务,我怀疑这并不是由于应用程序设计不佳所致。

这基本上是我的问题的核心,但以下是我对此主题的一些经验和思考,可以更详细地解释我的问题。

在我开发的所有应用程序中,只有一个应用程序确实需要服务。它是一个后台声音录制器,并且我将其作为前台服务与通知一起使用,因为我希望按钮能够控制它(例如音乐播放器所做的那样)。

除此之外,我从未真正看到过需要不断运行服务的要求,因为:

A)Intent 监听器(清单注册的BroadcastReceivers)是非常有用的特性,通常使用它们就足以满足许多用例(例如显示通知)。

B)如果必须进行定期执行,则可以订阅alarm事件。

C)我知道,在Android中,服务与Windows中的服务相比有很大的区别,因为在Android中,服务只是将您的代码组织起来,并让系统管理对象的生命周期。服务使用主线程,但通常在它们中生成新线程。

D)在开发文档中,建议使用服务进行网络通信和后台计算,但我不明白为什么不直接使用AsyncTasks进行这些操作。我非常喜欢它们,并广泛用于许多事情,从互联网下载数据到在时间关键条件下进行FFT计算。

E)我理解前台服务的有用性,但为什么人们使用后台服务这么多(除了系统应用)。

这些是我对“服务”的想法,我希望有更多经验的人能够解释这些优点和缺点(以及我可能错过的其他内容)。


我将自行放弃对该问题提出编辑建议,但请注意“then”与“than”的正确用法,至少令人不快。 - eriel marimon
5个回答

26
何时应该实现服务?
当您有工作需要完成以向用户提供价值时,例如:
- 需要一些时间来完成,可能比在希望完成工作的组件中拥有的时间更长,或者 - 在用户控制下提供该价值(例如,音乐播放器由 UI 中的播放/暂停按钮控制),或者 - 在极少数情况下,需要连续运行,因为它可以持续提供价值。
“有很多正在运行的服务,我怀疑这不仅是应用程序设计不良造成的。”
其中一些可能是由于技术误解或其他问题(例如让营销部门高兴,而不是让用户满意)导致的不良实现。
“这是一个背景声音录音机,我正在使用它作为前台服务与通知,因为我希望能够像音乐播放器一样控制按钮。”
在我看来,这是服务合理的使用方式。
“意图监听器是非常有用的功能,通常使用它们足以满足许多用例(例如显示通知)。”
我假设“意图监听器”是指在清单中注册的 BroadcastReceivers。在这种情况下,如果BroadcastReceiver需要执行的工作需要超过1毫秒以上,则应将该工作委托给IntentService进行处理。onReceive()方法在应用程序的主线程上调用,因此在清单中注册的BroadcastReceiver不能分离裸线程,因为进程可能在 onReceive() 方法返回后立即关闭。但是,在这些情况下,服务通常是短暂的(例如进行一些网络I/O和磁盘I/O,然后退出)。
“在开发文档中,建议使用服务进行网络通信和后台计算,但我不明白为什么不只是使用AsyncTasks来完成这些工作。”一个AsyncTask是处理以下情况的一个好的后台工作解决方案:
  • 由UI(activity或fragment)请求,且

  • 只需要少于一秒钟的时间,且

  • 非关键性任务

例如,如果你要下载头像来展示在ListView中,无论你是直接使用它们还是使用某个内部使用它们的图片获取库,使用AsyncTask都是一个不错的选择。
相反,如果用户通过你的应用购买了MP3文件,并且你需要下载该MP3文件,那么使用AsyncTask并不是一个好的解决方案。这可能需要超过一秒钟的时间。当下载正在进行时,用户可能会切换到其他应用程序(例如按 HOME 键)。此时,你的进程可以被终止...也许是在下载完成之前。使用IntentService管理下载是告诉操作系统你正在这里做真正的工作,为用户增加价值,因此进程将被保留一段时间。
请注意,如果后台工作可能需要15秒以上的时间,WakefulBroadcastReceiver或者我的WakefulIntentService是一个不错的选择,以避免设备在你试图完成这一任务时进入睡眠状态。

非常感谢您详细的解释和观点。您通过如此详细的解释帮助了我很多。关于意图监听器,是指在清单中注册的广播接收器。我也知道不应该在其中执行长时间运行的操作,尽管我不知道为什么您提到毫秒时间范围,因为我认为一秒以下的所有内容都可以。 - Igor Čordaš
因此,在服务和异步任务中执行操作的基本区别在于,异步任务依赖于应用/活动上下文,而服务不是,因此当应用程序在后台运行时,没有被杀死的危险。我不知道 WakefulBroadcastReceiver,我只是在适当的时候使用唤醒锁,这种方法也可以吗? - Igor Čordaš
@PSIXO:“虽然我不知道为什么你提到毫秒时间框架,因为我认为在1秒以下的所有内容都可以,对吗?”-- onReceive() 运行在主应用程序线程上。如果您在该线程上花费太多时间,就会丢失帧。花费约1秒钟将丢失约60帧,并且您的 UI 将在那 ~1s 内被冻结。当然,这只有在您的 UI 处于前台时才有意义...但是您的 onReceive() 代码不能假设您的 UI 不在前台。 - CommonsWare
@PSIXO:“AT是依赖于应用程序/活动上下文的,而服务不是,因此当应用程序在后台时,没有被杀死的危险?”-- 一个“服务”是告诉操作系统“我正在后台工作”的标志。而一个“活动”则不是。如果没有“服务”的情况下,“AsyncTask”完成任务之前,其进程被终止的风险要大得多。“我只是在适当的时候使用唤醒锁,这种方法也可以吗?”-- 是的,只要你不搞砸了就行 :-) 我提到的模式是经过测试和可靠的。 - CommonsWare
非常感谢,您的解释消除了我的所有疑虑。 “我正在后台工作”是对服务的相当好的解释。 我甚至在某个地方读到过一个滑稽版本:“服务对系统说:我正在这里做一些有用的事情,请不要杀死我。” 只有一件事情我仍然不是很清楚,为什么在任何安卓手机上都会有如此多正在运行的服务? - Igor Čordaš
@PSIXO:“我甚至在某个地方读到过‘服务告诉系统我正在做一些有用的事情,请不要杀死我’,这是一个滑稽版本。”——我可能写过这个。 “为什么任何安卓手机上都有这么多正在运行的服务?”——你得问它们的作者。短暂的服务执行一些事务性工作可能会很多,但通常不是问题。长期存在的服务有时确实是必需的,但也可能反映出编程不良,其中服务被有效地“泄漏”。不幸的是,这自从Android 1.0以来就一直存在问题。 - CommonsWare

4
我可以从我的经验中列举一些服务使用方式:
为了实现以下内容:
  1. 位置监听器,
  2. 声音模块,生成各种声音,
  3. 应用内内容更新,
  4. API,向其他应用提供服务,
  5. 应用内结算,
  6. 与 Web 服务通信(如果请求频率很高)。
实际上(除了第 5 条之外),它们都在整个应用程序的持续时间内运行,并且它们使用其他 Android 服务的一些功能,同时管理其状态。我认为这里重要的一点是在应用程序生命周期更改期间进行状态管理。
我更喜欢将 AsyncTasks 看作执行器(ExecutorService)的同类方法,它们应该按顺序执行并用于小任务。

谢谢你提供的好例子,但是最佳答案更加详细。虽然你的回答也很不错,所以我给了你一个赞。唯一让我困惑的是,为什么你要使用一个服务来提供API,难道BroadcastReceivers和Content providers不足够吗? - Igor Čordaš
1
说实话,我对API实现服务的选择并不太满意,AIDL文件的版本控制不太容易 - 你希望保持当前的API用户正常工作,所以不应该强制他们实现新的AIDL。此外,使用Service实现需要大量模板代码,这可能对一些只想使用一个函数的用户造成问题。另一方面,Service可能工作得更快,但我从未进行过这样的比较。我们计划将一些功能移动到Intents形式中,在某些地方看起来更合适。 - marcinj

0

如果考虑UI和绑定服务,您会认为两者都可以存在并且在某些时期不做任何事情。在这种情况下,您的UI可能会被重新创建很多次,但服务不会。这就是服务的重要性所在。比如说,您正在处理图像,然后旋转设备,您希望处理继续进行,而UI正在重新创建。您正在录制声音,然后旋转设备。这些都是我发现服务非常重要的地方。(有大量的重型数据处理、与Web交互,可能需要几秒钟)


0

在Android网站上,您可以找到一个表格,了解何时使用Service、Thread或WorkManager(用于调度作业的新API,截至本评论发布时仍处于alpha版)。https://developer.android.com/guide/background/#table-choose

该网站还指出,只有作为最后手段才需要使用已启动服务。未来,Android平台可能不支持已启动服务。请参考此链接https://developer.android.com/topic/performance/scheduling#services

应避免使用永久运行或执行定期工作的已启动服务,因为它们即使在不执行有用任务时也会继续使用设备资源。相反,您应该使用本页面描述的其他解决方案,并提供本机生命周期管理。只有在万不得已的情况下才使用已启动服务。未来,Android平台可能不支持已启动服务。


0

我建议将任何不涉及用户界面的操作移至后台线程,并确保该线程由Service启动和控制。话虽如此,根据情况我倾向于打破这个规则。例如,我总是无条件地从Service执行网络操作。然而,如果要写入的数据也是由用户界面生成的(例如编辑联系人信息),通常可以在Activity内部将数据写入本地存储,如内容提供程序或首选项文件。

另一个例子是在应用中播放音频时。一个Service通常控制音乐播放器的MediaPlayer或AudioTrack对象,而Activity负责处理游戏和应用程序中的音效。如果有疑问,通常将操作移到Service的后台线程是安全的。


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