Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord
Posted
技术标签:
【中文标题】Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord【英文标题】:Android 9 (Pie), Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord 【发布时间】:2019-09-17 13:30:51 【问题描述】:首先, 我看了这些;
Context.startForegroundService() did not then call Service.startForeground() Context.startForegroundService() did not then call Service.startForeground android 9 (Pie) Only: Context.startForegroundService() did not then call Service.startForeground() - Works fine on Oreo RemoteServiceException Context.startForegroundService() did not then call Service.startForeground() https://issuetracker.google.com/issues/76112072我有一个将近一百万人使用的流媒体应用程序。我正在为播放器使用前台服务。我还没有实现MediaSession
。我有 99.95% 的无崩溃会话。
所以这个应用程序适用于所有版本,但我开始收到 android 9 的崩溃报告 (ANR)。这种崩溃只发生在Samsung
手机,尤其是s9, s9+, s10, s10+, note9
型号。
我试过这些,
在onCreate()
中调用startForeground()
方法
在Context.stopService()
之前调用Service.startForeground()
类似问题的其他 *** 答案
我从 Google 的开发人员那里读到了一些 cmets,他们说只是 Intended Behavior
。我想知道它是由三星的系统还是 Android 操作系统发生的。有人对此有意见吗?我该如何解决这个问题?
【问题讨论】:
是否有不能调用 startForeground 的路径?还是主线程停滞/休眠/做太多工作无法处理服务启动的路径? 这是一个广播/音乐应用程序,它只是播放流 URL。我的手机没有崩溃。有用户表示在后台听音乐时发生崩溃 如果编写完美的应用程序有可能发生这种情况,我不会感到惊讶,因为无法保证您的代码会及时被调用。虽然this answer 中的特定代码部分是一团糟,但解决方法似乎有希望:通过绑定让服务运行,将其移动到前台,然后使用startForegroundService()
,然后取消绑定稍后。
对。通过 startForegroundService 启动前台服务以调用 startForeground I 服务后,您有一个短窗口。出于某种原因,这在这些设备上发生得不够快
如果这仅本地化到三星设备,则表明他们的自定义版本的 Android 9 AOSP 存在错误 - 而不是您的代码。在可以忠实再现之前,任何解决方法都只是猜测。
【参考方案1】:
在与这个崩溃进行了太多的斗争之后,我彻底修复了这个异常并找到了解决方案。
确保在您的服务中完成了这些工作,我将它们列出如下: (有些东西是重复的,正如另一个答案中提到的,我只是再写一遍)。
1- 呼叫
startForeground()
在 onCreate 和 onStartCommand 中。(可以多次调用 startForeground())
@Override
public void onCreate()
super.onCreate();
startCommand();
@Override
public int onStartCommand(Intent intent, int flags, int startId)
if (intent == null)
return START_NOT_STICKY;
final int command = intent.getIntExtra(MAIN_SERVICE_COMMAND_KEY, -1);
if (command == MAIN_SERVICE_START_COMMAND)
startCommand();
return START_STICKY;
return START_NOT_STICKY;
private void startCommand()
createNotificationAndStartForeground();
runningStatus.set(STARTED);
2- 停止您的服务使用
context.stopService()
,不需要调用stopForeground()或stopSelf()。
try
context.stopService(
new Intent(
context,
NavigationService.class
)
);
catch (Exception ex)
Crashlytics.logException(ex);
LogManager.e("Service manager can't stop service ", ex);
3- 启动您的服务使用
ContextCompat.startForegroundService()
它将处理不同的 API 版本。
ContextCompat.startForegroundService(
context,
NavigationService.getStartIntent(context)
);
4- 如果您的服务有操作(需要待处理的 Intent),则使用 广播接收器 处理您的 待处理的意图,而不是您当前的服务(它将调用您的服务Create() 并且可能很危险,或者使用 PendingIntent.FLAG_NO_CREATE) ,最好使用特定的广播接收器来处理您的服务通知操作,我的意思是使用 PendingIntent.getBroadcast()强>。
private PendingIntent getStopActionPendingIntent()
final Intent stopNotificationIntent = getBroadCastIntent();
stopNotificationIntent.setAction(BROADCAST_STOP_SERVICE);
return getPendingIntent(stopNotificationIntent);
private PendingIntent getPendingIntent(final Intent intent)
return PendingIntent.getBroadcast(
this,
0,
intent,
0
);
new NotificationCompat.Builder(this, CHANNEL_ID)
.addAction(
new NotificationCompat.Action(
R.drawable.notification,
getString(R.string.switch_off),
getStopActionPendingIntent()
)
)
5- 始终在停止您的服务之前确保您的服务已创建并启动(我创建了一个具有我的服务状态的全局类)
if (navigationServiceStatus == STARTED)
serviceManager.stopNavigationService();
6- 将您的 notificationId 设置为长数字,例如 121412。
7- 使用 NotificationCompat.Builder 将处理不同的 API 版本,您只需要为构建版本创建通知通道 >= Build.VERSION_CODES.O.(这不是一个解决方案,只是让您的代码更具可读性)
8- 添加
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
权限您的清单。 (这个在 android 文档中有提到) Android Foreground Service
希望对你有帮助:))
【讨论】:
为什么我们必须在 onCreate 和 onStartCommand 中都这样做? @Sepehr 什么是 MAIN_SERVICE_COMMAND_KEY ? awwe,很高兴在很长一段时间后在这里见到你 Sepehr...感谢它在 onStartCommand 中添加 startForegroundService 对我来说就像一个魅力。【参考方案2】:我正在等待我的崩溃报告来分享解决方案。将近 20 天我没有遇到任何崩溃或 ANR。我想分享我的解决方案。它可以帮助遇到这个问题的人。
在onCreate()
方法中
onCreate()
的顶部创建一个通知渠道。 Official doc
我在Context.startForegroundService()
方法之后调用Service.startForeground()
方法。在我的prepareAndStartForeground()
方法中。
注意:我不知道为什么,但是 ContextCompat.startForegroundService() 不能正常工作。
出于这个原因,我已经手动将相同的函数添加到我的服务类中,而不是调用ContextCompat.startForegroundService()
private fun startForegroundService(intent: Intent)
if (Build.VERSION.SDK_INT >= 26)
context.startForegroundService(intent)
else
// Pre-O behavior.
context.startService(intent)
prepareAndStartForeground()
方法
private fun prepareAndStartForeground()
try
val intent = Intent(ctx, MusicService::class.java)
startForegroundService(intent)
val n = mNotificationBuilder.build()
// do sth
startForeground(Define.NOTIFICATION_ID, n)
catch (e: Exception)
Log.e(TAG, "startForegroundNotification: " + e.message)
这是我的onCreate()
override fun onCreate()
super.onCreate()
createNotificationChannel()
prepareAndStartForeground()
我的onStartCommand()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
if (intent == null)
return START_STICKY_COMPATIBILITY
else
//....
//...
return START_STICKY
onRebind
、onBind
、onUnbind
之类的方法
internal var binder: IBinder? = null
override fun onRebind(intent: Intent)
stopForeground(true) // <- remove notification
override fun onBind(intent: Intent): IBinder?
stopForeground(true) // <- remove notification
return binder
override fun onUnbind(intent: Intent): Boolean
prepareAndStartForeground() // <- show notification again
return true
onDestroy() 调用时我们需要清除一些东西
override fun onDestroy()
super.onDestroy()
releaseService()
private fun releaseService()
stopMedia()
stopTimer()
// sth like these
player = null
mContext = null
afChangeListener = null
mAudioBecomingNoisy = null
handler = null
mNotificationBuilder = null
mNotificationManager = null
mInstance = null
我希望此解决方案适合您。
【讨论】:
感谢分享您的实施。似乎您有基于绑定的连接。也许这可能会导致主要区别,否则相同的解决方案对您有效。感谢分享! 嗨,“我不知道为什么,但 ContextCompat.startForegroundService() 无法正常工作”的任何具体原因。 您好,您能具体说明一下您是如何创建此服务的吗?因为服务onCreate方法是在调用后调用的:ContextCompat.startForegroundService(context, intent),而这里你调用的是:你的服务onCreate上的startForegroundService。 @ingsaurabh 实际上,我不知道,因为我在日志中没有看到任何差异或发生任何崩溃。我基本上只能说 ContextCompat.startForegroundService() 不适用于我的代码:) @Beyazid 谢谢你的回答,但我不确定我是否理解。您示例中的 onCreate 方法是 MusicService 的 onCreate 吗?因为如果是,我看起来像你两次调用 startForegroundService 。一次在应用程序中形成不同的位置(为了“触发”服务 onCreate),第二次在服务 onCreate 中形成。【参考方案3】:Android Service
组件要正常工作有点棘手,尤其是在操作系统添加了额外限制的更高版本的 Android 上。如其他答案中所述,启动Service
时,请使用ContextCompat.startForegroundService()
。接下来,在Service.onStartCommand()
中,立即致电startForeground()
。将要显示的Notification
存储为成员字段并使用它,除非它为空。示例:
private var notification:Notification? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
if (notification == null)
notification = createDefaultNotification()
startForeground(NOTIFICATION_ID, notification)
// Do any additional setup and work herre
return START_STICKY
始终在您的Service
中返回START_STICKY
。其他任何事情都可能是错误的,特别是如果您正在制作任何类型的音频播放器。事实上,如果你正在做一个音频播放器,你不应该实现自己的 Service,而是使用 MediaBrowserServiceCompat
(来自 AndroidX)。
我也推荐我在这上面写的博文:https://hellsoft.se/how-to-service-on-android-part-3-1e24113152cd
【讨论】:
始终在您的服务中返回 START_STICKY。还有什么可能是错误的,这是相当冒昧的,需要解释一下吗? @Tim 请阅读我链接到的帖子。在该系列中,我解释了为什么START_STICKY
是您今天应该使用的。当然也有例外,但对于 99% 的需要 Service
的情况,这是正确的做法。【参考方案4】:
在使用相同的手机遇到同样的问题后,我做了一些更改,并且崩溃消失了。我不确定是什么造成的,但我猜是打电话 onCreate 和 onStartCommand 中的 startForeground。如果服务已经启动并且在 onCreate 中正确调用了所有内容,我不确定为什么需要这样做。
其他变化: - 将 serviceId 更改为某个较低的数字 (1-10) - 通过单例同步类调用 startFororegroundService 的频率较低(这是在崩溃之前实现的,以防止在使用来自 onStartCommand 的回调开始之前停止服务,但现在如果服务已经启动,它也会过滤调用)。 - 使用 START_REDELIVER_INTENT(不应该影响任何东西)
上述问题仅针对部分用户出现在上述手机上,因此我怀疑这与三星的一些新更新有关,最终将得到修复
【讨论】:
如果Service已经创建,调用Context.startForegroundService()需要startForeground onStartCommand。【参考方案5】:我几乎已经消除了 MediaSessionCompat.Callback 方法中 startForeground() 的问题,例如 onPlay()、onPause()。
【讨论】:
【参考方案6】:根据此错误消息,当您调用 Context.startForegroundService() 时,您必须使用 Service.startForeground() 方法发出通知。这是我理解的。
【讨论】:
【参考方案7】:我浪费了很多时间来弄清楚它为什么会崩溃,但不明白为什么。问题是startForeground()
的通知 ID。将其更改为 0 以外的值。这是在 Android 11 上运行的。
@Override
public int onStartCommand(Intent intent, int flags, int startId)
//Create notification here
startForeground(5, builder.build()); //Change the ID to something other than 0
return START_NOT_STICKY;
【讨论】:
以上是关于Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Android 9.0(PIE) 中获取 WIFI SSID?
Android Api(Okhttps)未在android 9(pie)及更高版本中调用[重复]
为啥 Android Studio 不会在 Android Pie (9.0) 上运行应用程序?
Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord
Android 9 Pie 崩溃 (com.google.android.gms...ClassNotFoundException)