问题描述
Android R平台 收音机启动前台服务 startForegroundService() 报错
E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{990dd99 u0 com.android.fmradio/.FmService}
问题分析
在Android O中,有一个新的背景限制。尝试启动startService()时,将获得IlleagalStateException,因此现在应使用startForegroundService(),但是如果通过此新方法启动服务,则会在屏幕截图中看到类似的错误。为避免此异常,您需要在startForegroundService()之后有5秒钟的时间来创建startForeground(),以通知用户您正在后台工作。否则将崩溃出现这样的崩溃信息。
使用方法
- 权限
启动前台服务需要添加如下权限:
<uses-permission android:name=".FOREGROUND_SERVICE"/>
这是一个自 Android 9.0 (API level 28) 引入的新权限,用于确保应用程序不会在未经用户许可的情况下创建前台服务。如果您的应用程序未声明此权限并尝试启动前台服务,则可能会引发 SecurityException 异常。
需注意,自Android O(API级别26)以后,仅仅在中声明权限是不够的,还必须在代码中请求权限。可以按以下方式检查和请求权限:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission not yet granted, request it
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.FOREGROUND_SERVICE},
MY_PERMISSIONS_REQUEST_FOREGROUND_SERVICE);
}
- 启动服务
在 Android 8.0 及以上版本,为了遵循后台限制策略,应用程序必须使用 startForegroundService() 方法来启动前台服务,而不能使用 startService()。
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
- service服务中的Notification
在 API 26 及以上版本,首先需要创建一个 NotificationChannel 对象,并为其设置一些属性(例如声音、震动等)。然后用该 Channel 创建一个 Notification,并调用 startForeground() 方法将其赋给前台服务。这样,即使在设备处于 Doze 策略模式时,该服务仍能保持活动状态。在 API 26 以下版本,直接调用 startService() 方法即可正常启动服务。
值得注意的是,为了满足前台服务的要求,通知必须包含在 Notification 对象中。通知提供了有关服务当前状态和活动实例的信息,以便用户随时可以查看和监视该服务的运行情况。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 创建一个 NotificationChannel
NotificationChannel channel = new NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_DEFAULT);
// 自定义设置通知声音、震动等
channel.enableVibration(true);
channel.setVibrationPattern(new long[]{100, 200});
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
// 构建一个 Notification
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("Some Service")
.setContentText("服务正在运行...")
.setSmallIcon(R.drawable.ic_notification)
.build();
// 启动前台服务
// 通知栏标识符 前台进程对象唯一SERVICE_ID
startForeground(SERVICE_ID, notification);
} else {
startService(intent); // API < 26 直接启动服务即可
}
注意事项
启动一个前台服务要做的事情有:
(1) 调用 () 可以创建一个前台服务,相当于创建一个后台服务并将它推到前台;
(2) 创建一个用户可见的 Notification ;
(3) 必须在5秒内调用该服务的 startForeground(int id, Notification notification)
方法,否则将停止服务并抛出 :() did not then call ()
异常。
- 如果在 5 秒内没有调用startForeground(),timeout 就会触发,会报出ANR
- 一旦通过startForegroundService() 启动前台服务,必须在service 中有startForeground() 配套,不然会出现ANR 或者crash
- startForeground() 中的id 和notification 不能为0 和 null
相关拓展
类别 | 区别 | 应用 |
---|---|---|
前台服务 | 会在通知一栏显示 ONGOING 的 Notification, 当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。 | 常见的如音乐播放服务 |
后台服务 | 默认的服务即为后台服务,即不会在通知一栏显示 ONGOING 的 Notification。 当服务被终止的时候,用户是看不到效果的。 | 某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等。 |
startForegroundService()
与 startService()
是 Android 中用于启动服务的两种方法,它们之间的主要区别在于:
-
API 级别:
startForegroundService()
方法是在 Android O (API 级别 26) 引入的,而startService()
方法则是基于早期版本的 API 设计的。 -
后台限制策略:为了避免系统和用户资源被滥用,Android 8.0(API 级别 26)以上版本中增加了一些针对后台服务的限制策略。在这些限制策略下,应用程序必须使用
startForegroundService()
方法来启动前台服务,而不能使用startService()
。 -
前台通知:
startForegroundService()
方法需要与startForeground()
方法配合使用,以在状态栏中创建前台通知。前台通知可让用户明确知道应用正在执行某些操作,并防止系统将其进程或服务结束或处于 Doze 模式。而startService()
则不会创建前台通知,因此系统可能会更轻易地终止其进程或服务。
因此,如果您计划启动一个长时间运行的服务或者希望保持服务在设备休眠时仍然运行,可以考虑使用 startForegroundService()
和 startForeground()
方法。如果只是启动一个短暂的服务并且不需要该服务在设备休眠时仍然保持运行,则可以使用 startService()
方法。
需要注意的是,即使您使用了 startForegroundService()
方法,系统也会在必要时终止其进程或服务。因此,在设计应用程序时,应该考虑将服务设计为尽可能少消耗系统资源,并及时释放不必要的资源来避免出现资源过度占用等问题。