在查看Google问题和许多其他SO上的问题之后,我得出了我在底部添加的解决方案。
我要处理的是以下内容:
- 在
onCreate
和onStartCommand
中,如果服务尚未在前台,则确保将其移动到前台 - 我不仅仅停止服务,我使用额外的命令发送给
onStartCommand
处理程序来确保服务不会在启动时停止(在它完成前往前台之前) - 我从不直接停止服务(
context.stopService(...)
),我总是通过运行服务本身的命令来停止服务-因此我确保它只能在运行在前台时停止,而不是在启动期间 - 一个服务只能停止一次
- 我通过取消其前台通知并仅随后停止服务本身来停止服务
我个人使用该服务进行覆盖,因此我不在类内处理绑定器,因为我不使用它们。
我得到的日志
2019-07-29 21:41:27,146 [[BaseOverlayService:62 onCreate]]: onCreate
2019-07-29 21:41:27,146 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by onCreate)
2019-07-29 21:41:27,152 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by onCreate) - moved to foreground: true
2019-07-29 21:41:27,176 [[BaseOverlayService:79 onStartCommand]]: onStartCommand: isForeground: true | action: null | isStopping: false
2019-07-29 21:41:27,945 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by updateNotification [OverlayService [onInitFinished]])
2019-07-29 21:41:27,947 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by updateNotification [OverlayService [onInitFinished]]) - moved to foreground: false
这是一个崩溃报告的日志-可以看到,在第3行中,服务被移至前台(
moved to foreground: true
),并且在第6行中已经知道它正在前台运行。我在我的Android 9设备上大量使用此应用程序(24/7,它一直在运行),并且没有问题。自从我使用下面的基类以来,问题已经真正减少到每月总共只有几次崩溃。尽管如此,以上日志显示,我的服务在毫秒内运行在前台,但仍可能会崩溃,如下所示:
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{86fa711 u0 com.my.app/com.my.app.service.OverlayService}
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1855)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6986)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
你们有人发现我的基类存在任何问题吗?
代码
abstract class BaseOverlayService<T : BaseOverlayService<T>>(
val foregroundNotificationId: Int,
val notificationCreator: ((service: T) -> Notification)
) : Service() {
companion object {
val DEBUG = true
// helper function, simply checks if this service is already running by checking the ActivityManager
inline fun <reified T : BaseOverlayService<T>> isRunning(context: Context): Boolean {
return Tools.isServiceRunning(context, T::class.java)
}
inline fun <reified T : BaseOverlayService<T>> start(context: Context, checkIfServiceIsRunning: Boolean) {
if (checkIfServiceIsRunning && isRunning<T>(context)) {
L.logIf { DEBUG }?.d { "IGNORED start intent" }
return
}
L.logIf { DEBUG }?.d { "send start intent" }
val intent = Intent(context, T::class.java)
ContextCompat.startForegroundService(context, intent)
}
inline fun <reified T : BaseOverlayService<T>> sendAction(context: Context, checkIfServiceIsRunning: Boolean, action: String, intentUpdater: ((Intent) -> Unit) = {}) {
if (checkIfServiceIsRunning && !isRunning<T>(context)) {
L.logIf { DEBUG }?.d { "IGNORED action intent - action: $action" }
return
}
L.logIf { DEBUG }?.d { "send action intent - action: $action" }
val intent = Intent(context, T::class.java)
intent.action = action
intentUpdater(intent)
ContextCompat.startForegroundService(context, intent)
}
}
protected var isForeground = false
private set
protected var isStopping: Boolean = false
private set
// ------------------------
// service events
// ------------------------
final override fun onCreate() {
L.logIf { DEBUG }?.d { "onCreate" }
super.onCreate()
if (foregroundNotificationId <= 0) {
throw RuntimeException("foregroundNotificationId must be > 0!")
}
moveToForeground("onCreate")
onCreateEvent()
}
final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val returnValue = START_STICKY
L.logIf { DEBUG }?.d { "onStartCommand: isForeground: $isForeground | action: ${intent?.action} | isStopping: $isStopping" }
// 1) if service is stopping, we ignore the event
if (isStopping) {
return returnValue
}
// 2) if service is not running in foreground we make it run in the foreground
if (!isForeground) {
moveToForeground("onStartCommand")
}
onStartCommandEvent(intent, flags, startId)
return returnValue
}
final override fun onBind(intent: Intent): IBinder? {
// overlay service is never bound!
return null
}
// ------------------------
// Forwarded abstract events
// ------------------------
abstract fun onCreateEvent()
abstract fun onStartCommandEvent(intent: Intent?, flags: Int, startId: Int)
abstract fun onStopEvent()
// ------------------------
// protected functions
// ------------------------
protected fun stopService() {
L.logIf { DEBUG }?.d { "stopService | isStopping: $isStopping" }
if (isStopping) {
L.logIf { DEBUG }?.d { "IGNORED stopService" }
return
}
onStopEvent()
isStopping = true
moveToBackground(true)
stopSelf()
L.logIf { DEBUG }?.d { "stopService finished" }
}
protected fun updateNotification(caller: String) {
moveToForeground("updateNotification [$caller]")
}
// ------------------------
// private foreground/background functions
// ------------------------
private fun moveToForeground(caller: String): Boolean {
L.logIf { DEBUG }?.d { "BEFORE moveToForeground (called by $caller)" }
// 1) Create notification
val notification = notificationCreator(this as T)
// 2.1) Create foreground notification
val result = if (!isForeground) {
isForeground = true
startForeground(foregroundNotificationId, notification)
true
}
// 2.2) Update foreground notification
else {
notificationManager.notify(foregroundNotificationId, notification)
false
}
L.logIf { DEBUG }?.d { "AFTER moveToForeground (called by $caller) - moved to foreground: $result" }
return result
}
private fun moveToBackground(cancelNotification: Boolean) {
isForeground = false
super.stopForeground(cancelNotification)
}
// ------------------------
// private helper functions
// ------------------------
private val notificationManager by lazy {
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
}