context . start前台服务()没有调用service . start前台()

我在Android O操作系统上使用Service类。

我计划在后台使用Service

安卓系统文档声明

如果你的应用程序的API级别为26或更高,系统会对使用或创建后台服务施加限制,除非应用程序本身在前台。如果应用程序需要创建前台服务,该应用程序应该调用startForegroundService()

如果使用startForegroundService()Service会抛出以下错误。

Context.startForegroundService() did not then call
Service.startForeground()

这有什么问题?

274718 次浏览

从谷歌的文档Android 8.0行为改变:

系统允许应用程序调用Context.startForegroundService(),即使应用程序处于后台。但是,应用程序必须在服务创建后的5秒内调用该服务的startForeground()方法。

< p >解决方案: 对于Context.startForegroundService()

所使用的Service,在onCreate()中调用startForeground()

另见:后台执行限制 for Android 8.0 (Oreo)

我调用ContextCompat.startForegroundService(this, intent)来启动服务

在服务onCreate

 @Override
public void onCreate() {
super.onCreate();


if (Build.VERSION.SDK_INT >= 26) {
String CHANNEL_ID = "my_channel_01";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT);


((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);


Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("")
.setContentText("").build();


startForeground(1, notification);
}
}

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

类似于startService(Intent),但是有一个隐含的承诺 Service将调用start前台(int, android.app.Notification)一次 它开始运行。该服务被赋予了相当的时间 到ANR区间做这个,否则系统会 自动停止服务并声明应用程序ANR 与普通的startService(Intent)不同,这个方法可以在 任何时候,不管托管服务的应用程序是否在 前景状态。< / p >

确保你在onCreate()上调用Service.startForeground(int, android.app.Notification),这样你就可以确保它会被调用..如果你有任何情况可能会阻止你这样做,那么你最好使用正常的Context.startService(Intent)并自己调用Service.startForeground(int, android.app.Notification)

似乎Context.startForegroundService()添加了一个看门狗,以确保你在Service.startForeground(int, android.app.Notification)被销毁之前调用它…

如果你调用Context.startForegroundService(...),然后在调用Service.startForeground(...)之前调用Context.stopService(...),你的应用程序将崩溃。

我有一个清晰的复制在这里ForegroundServiceAPI26

我在:谷歌问题跟踪器上打开了一个错误

这方面的几个bug已经被打开和关闭不会修复。

希望我的与明确的复制步骤将使削减。

谷歌团队提供的信息

谷歌issuetracker Comment 36 . br . br ="https://issuetracker.google.com/issues/76112072#comment36" rel="noreferrer">谷歌issuetracker

这不是一个框架错误;这是故意的。如果应用程序使用startForegroundService()启动一个服务实例,它必须将该服务实例转换到前台状态并显示通知。如果服务实例在调用startForeground()之前停止,则该承诺未实现:这是应用程序中的一个错误。

再保险# 31,发布一个其他应用程序可以直接启动的服务从根本上来说是不安全的。您可以通过将该服务的所有启动操作视为需要startForeground()来减轻这一点,尽管显然这可能不是您想要的。

谷歌issuetracker Comment 56 . br . br ="https://issuetracker.google.com/issues/76112072#comment56" rel="noreferrer">谷歌issuetracker

这里有几种不同的情况会导致相同的结果。

完全的语义问题是,用startForegroundService()启动某项工作,但忽略了通过startForeground()实际将其转换到前台,这只是一个语义问题。这被故意视为应用程序漏洞。在将服务转移到前台之前停止服务是应用程序错误。这是OP的关键,也是为什么这个问题被标记为“按预期工作”。

然而,也有关于虚假的检测这个问题的问题。这是被视为一个真正的问题,尽管它是与这个特定的错误跟踪器问题分开跟踪的。我们对抱怨不是充耳不闻。

现在在Android O,你可以设置后台限制如下

调用服务类的服务

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
SettingActivity.this.startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}

服务类应该是这样的

public class DetectedService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}


@Override
public void onCreate() {
super.onCreate();
int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
}




// Do whatever you want to do here
}
}

我也面临着同样的问题,花时间找到了一个解决方案,你可以尝试下面的代码。如果你使用Service,然后将此代码放在onCreate中,否则你使用Intent Service,然后将此代码放在onHandleIntent中。

if (Build.VERSION.SDK_INT >= 26) {
String CHANNEL_ID = "my_app";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
"MyApp", NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("")
.setContentText("").build();
startForeground(1, notification);
}
只是提醒一下,我在这上面浪费了太多时间。我一直得到这个异常,即使我调用startForeground(..)作为onCreate(..)中的第一件事。 最后我发现这个问题是由使用NOTIFICATION_ID = 0引起的。使用任何其他值似乎都可以解决这个问题

在Android 8+上,当调用服务。start前台(int id,通知通知)id设置为0时,也会发生此错误。

id int:该通知的标识符,根据NotificationManager。通知(int、通知);一定不能为0

为什么这个问题会发生,因为Android框架不能保证你的服务在5秒内启动,但另一方面,框架确实有严格的限制前台通知必须在5秒内触发,而不检查框架是否试图启动服务。

这绝对是一个框架问题,但并不是所有面临这个问题的开发人员都尽力了:

  1. startForeground通知必须同时在onCreateonStartCommand中,因为如果你的服务已经创建,并且你的活动试图再次启动它,onCreate将不会被调用。

  2. 通知ID不能为0,否则即使原因不同,也会发生相同的崩溃。

  3. stopSelf不能在startForeground之前调用。

有了以上3个,这个问题可以减少一点,但仍然不是一个解决方案,真正的解决方案或让我们说的变通办法是把你的目标sdk版本降低到25。

请注意,Android P很可能仍然会有这个问题,因为谷歌甚至拒绝理解正在发生什么,不相信这是他们的错,阅读# 36# 56以获得更多信息

很多答案,但没有一个对我有用。

我已经开始这样服务了。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}

在我的服务在onStartCommand

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText("SmartTracker Running")
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(NOTIFICATION_ID, notification);
} else {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("SmartTracker is Running...")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(NOTIFICATION_ID, notification);
}

不要忘记将NOTIFICATION_ID设置为非零

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

所以一切都是完美的,但仍然崩溃在8.1,所以原因如下。

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
stopForeground(true);
}

我已经调用停止前台删除通知,但一旦通知删除服务成为后台和后台服务不能从后台运行在android O。在收到推送后启动。

神奇的词是

   stopSelf();

到目前为止,任何原因你的服务崩溃遵循以上所有步骤,并享受。

即使在Service中调用startForeground之后,如果我们在onCreate被调用之前调用stopService,它也会在某些设备上崩溃。 因此,我通过使用一个附加标志启动服务来修复这个问题:

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

并在onStartCommand中添加了一个检查,看看它是否已经开始停止:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//call startForeground first
if (intent != null) {
boolean stopService = intent.getBooleanExtra("request_stop", false);
if (stopService) {
stopSelf();
}
}


//Continue with the background task
return START_STICKY;
}

附注:如果服务没有运行,它将首先启动服务,这是一种开销。

Android O API 26的问题

如果你立即停止服务(所以你的服务实际上并没有真正运行(措辞/理解),并且你在ANR间隔之下,你仍然需要在stopSelf之前调用start前台

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

尝试了这种方法,但它仍然创建一个错误:-

if (Util.SDK_INT > 26) {
mContext.startForegroundService(playIntent);
} else {
mContext.startService(playIntent);
}

我正在使用这个,直到错误被解决

mContext.startService(playIntent);

我只是检查PendingIntent null或nor not之前调用 context.startForegroundService(service_intent)函数。< / p >

这对我很有用

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
context.startForegroundService(service_intent);
}
else
{
context.startService(service_intent);
}
}

我有一个解决这个问题的办法。我已经在自己的应用(DAU超过30万)中验证了这一修复方法,它至少可以减少95%的这种崩溃,但仍然不能100%避免这个问题。

即使您确保在服务启动后调用start前台(),也会发生此问题。这可能是因为在很多情况下,服务创建和初始化过程已经花费了超过5秒的时间,那么无论何时何地调用startForeground()方法,这个崩溃都是不可避免的。

我的解决方案是确保start前台()将在startForegroundService()方法后的5秒内执行,无论您的服务需要创建和初始化多长时间。下面是详细的解决方案。

  1. 首先不要使用startForegroundService,而是使用带有auto_create标志的bindService()。它将等待服务初始化。下面是代码,我的样本服务是MusicService:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
    if (binder instanceof MusicBinder) {
    MusicBinder musicBinder = (MusicBinder) binder;
    MusicService service = musicBinder.getService();
    if (service != null) {
    // start a command such as music play or pause.
    service.startCommand(command);
    // force the service to run in foreground here.
    // the service is already initialized when bind and auto_create.
    service.forceForeground();
    }
    }
    applicationContext.unbindService(this);
    }
    
    
    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
    }, Context.BIND_AUTO_CREATE);
    
  2. Then here is MusicBinder implementation:

    /**
    * Use weak reference to avoid binder service leak.
    */
    public class MusicBinder extends Binder {
    
    
    private WeakReference<MusicService> weakService;
    
    
    /**
    * Inject service instance to weak reference.
    */
    public void onBind(MusicService service) {
    this.weakService = new WeakReference<>(service);
    }
    
    
    public MusicService getService() {
    return weakService == null ? null : weakService.get();
    }
    }
    
  3. The most important part, MusicService implementation, forceForeground() method will ensure that startForeground() method is called just after startForegroundService():

    public class MusicService extends MediaBrowserServiceCompat {
    ...
    private final MusicBinder musicBind = new MusicBinder();
    ...
    @Override
    public IBinder onBind(Intent intent) {
    musicBind.onBind(this);
    return musicBind;
    }
    ...
    public void forceForeground() {
    // API lower than 26 do not need this work around.
    if (Build.VERSION.SDK_INT >= 26) {
    Intent intent = new Intent(this, MusicService.class);
    // service has already been initialized.
    // startForeground method should be called within 5 seconds.
    ContextCompat.startForegroundService(this, intent);
    Notification notification = mNotificationHandler.createNotification(this);
    // call startForeground just after startForegroundService.
    startForeground(Constants.NOTIFICATION_ID, notification);
    }
    }
    }
    
  4. If you want to run the step 1 code snippet in a pending intent, such as if you want to start a foreground service in a widget (a click on widget button) without opening your app, you can wrap the code snippet in a broadcast receiver, and fire a broadcast event instead of start service command.

That is all. Hope it helps. Good luck.

我有一个小部件,它在设备清醒时进行相对频繁的更新,我在短短几天内看到了数千次崩溃。

问题触发

我甚至在我的Pixel 3 XL上也注意到了这个问题,而我本以为这款设备负载并不大。并且任何和所有代码路径都被startForeground()覆盖。但后来我意识到,在很多情况下,我的服务可以很快地完成工作。我认为触发我的应用程序的原因是,在系统真正抽出时间显示通知之前,服务已经结束。

解决方案/解决方案

我摆脱了所有的崩溃。我所做的就是删除对stopSelf()的调用。(我正在考虑延迟停止,直到我非常确定通知被显示,但我不希望用户看到通知,如果它不是必要的。)当服务空闲一分钟或系统正常销毁它而不抛出任何异常时。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
stopSelf();
}

我一直在研究这个问题,这是我目前为止的发现。如果我们有类似这样的代码,就会发生崩溃:

MyForegroundService.java

public class MyForegroundService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(...);
}
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

异常在以下代码块中抛出:

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
...
if (r.fgRequired) {
Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
+ r);
r.fgRequired = false;
r.fgWaiting = false;
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
mAm.mHandler.removeMessages(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
if (r.app != null) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
msg.obj = r.app;
msg.getData().putCharSequence(
ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
mAm.mHandler.sendMessage(msg);
}
}
...
}

此方法在MyForegroundServiceonCreate()之前执行,因为Android将服务的创建安排在主线程处理程序上,但bringDownServiceLocked是在BinderThread上调用的,这是一个竞态条件。这意味着MyForegroundService没有机会调用startForeground,这将导致崩溃。

为了解决这个问题,我们必须确保在MyForegroundServiceonCreate()之前没有调用bringDownServiceLocked

public class MyForegroundService extends Service {


private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";


private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
context.removeStickyBroadcast(intent);
stopForeground(true);
stopSelf();
}
};


@Override
public void onCreate() {
super.onCreate();
startForeground(...);
registerReceiver(
stopReceiver, new IntentFilter(ACTION_STOP));
}


@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(stopReceiver);
}


public static void stop(Context context) {
context.sendStickyBroadcast(new Intent(ACTION_STOP));
}
}

通过使用sticky广播,我们可以确保广播不会丢失,并且一旦stopReceiverMyForegroundServiceonCreate()中注册了它,它就会接收到停止意图。此时我们已经调用了startForeground(...)。我们还必须删除这个粘滞的广播,以防止下一次stopReceiver被通知。

sendStickyBroadcast方法已弃用,我只使用它作为临时解决方法来修复此问题。

请不要在onCreate ()方法中调用任何StartForgroundServices,你必须在创建工作线程后在onStartCommand ()中调用StartForground services,否则你总是会得到ANR,所以请不要在onStartCommand ()的主线程中编写复杂的登录;

public class Services extends Service {


private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}




@Override
public int onStartCommand(Intent intent, int flags, int startId) {


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText("SmartTracker Running")
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(1, notification);
Log.e("home_button","home button");
} else {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("SmartTracker is Running...")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(1, notification);
Log.e("home_button_value","home_button_value");


}
return super.onStartCommand(intent, flags, startId);


}
}

谨慎!start前台函数不能以0作为第一个参数,它将引发异常!这个例子包含错误的函数调用,将0更改为你自己的const,它不能为0或大于Max(Int32)

我已经修复了用startService(intent)而不是Context.startForeground()启动服务的问题,并在super.OnCreate()之后立即调用startForegound()。此外,如果您在引导时启动服务,您可以启动在引导广播时启动服务的Activity。虽然这不是一个永久的解决方案,但它是有效的。

只要在创建Service或IntentService后立即调用start前台方法。是这样的:

import android.app.Notification;
public class AuthenticationService extends Service {


@Override
public void onCreate() {
super.onCreate();
startForeground(1,new Notification());
}
}

服务

class TestService : Service() {


override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")


val nBuilder = NotificationCompat.Builder(this, "all")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("TestService")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
startForeground(1337, nBuilder.build())
}


override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val rtn = super.onStartCommand(intent, flags, startId)


if (intent?.action == STOP_ACTION) {
Log.d(TAG, "onStartCommand -> STOP")
stopForeground(true)
stopSelf()
} else {
Log.d(TAG, "onStartCommand -> START")
}


return rtn
}


override fun onDestroy() {
Log.d(TAG, "onDestroy")
super.onDestroy()
}


override fun onBind(intent: Intent?): IBinder? = null


companion object {


private val TAG = "TestService"
private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"


fun start(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
}


fun stop(context: Context) {
val intent = Intent(context, TestService::class.java)
intent.action = STOP_ACTION
ContextCompat.startForegroundService(context, intent)
}


}


}

测试人员

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)


start_test_service.setOnClickListener {
TestService.start(this@MainActivity)
TestService.stop(this@MainActivity)
}

结果

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy

更新onStartCommand(...)中的数据

onBind(…)

与__abcc2相比,onBind(...)是一个更好的生命周期事件,用于初始化startForeground,因为onBind(...)传入的Intent可能包含初始化Service所需的Bundle中的重要数据。然而,这并不是必需的,因为onStartCommand(...)是在Service第一次创建或随后多次调用时调用的。

onStartCommand(…)

onStartCommand(...)中的startForeground非常重要,以便在Service创建后更新它。

在创建Service后调用ContextCompat.startForegroundService(...)时,不会调用onBind(...)onCreate(...)。因此,更新后的数据可以通过Intent Bundle传递到onStartCommand(...)来更新Service中的数据。

样本

我使用这个模式在< em > < >强Coinverse < /强> < / em >加密货币新闻应用程序中实现PlayerNotificationManager

Activity / Fragment.kt

context?.bindService(
Intent(context, AudioService::class.java),
serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
context!!,
Intent(context, AudioService::class.java).apply {
action = CONTENT_SELECTED_ACTION
putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
audioUrl = uri.toString()
})
})

AudioService.kt

private var uri: Uri = Uri.parse("")


override fun onBind(intent: Intent?) =
AudioServiceBinder().apply {
player = ExoPlayerFactory.newSimpleInstance(
applicationContext,
AudioOnlyRenderersFactory(applicationContext),
DefaultTrackSelector())
}


override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.let {
when (intent.action) {
CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
val intentUri = Uri.parse(content.audioUrl)
// Checks whether to update Uri passed in Intent Bundle.
if (!intentUri.equals(uri)) {
uri = intentUri
player?.prepare(ProgressiveMediaSource.Factory(
DefaultDataSourceFactory(
this,
Util.getUserAgent(this, getString(app_name))))
.createMediaSource(uri))
player?.playWhenReady = true
// Calling 'startForeground' in 'buildNotification(...)'.
buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
}
}
}
}
return super.onStartCommand(intent, flags, startId)
}


// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
this,
content.title,
app_name,
if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun createCurrentContentIntent(player: Player?) = ...
override fun getCurrentContentText(player: Player?) = ...
override fun getCurrentContentTitle(player: Player?) = ...
override fun getCurrentLargeIcon(player: Player?,
callback: PlayerNotificationManager.BitmapCallback?) = ...
},
object : PlayerNotificationManager.NotificationListener {
override fun onNotificationStarted(notificationId: Int, notification: Notification) {
startForeground(notificationId, notification)
}
override fun onNotificationCancelled(notificationId: Int) {
stopForeground(true)
stopSelf()
}
})
return playerNotificationManager.setPlayer(player)
}

我知道,已经发布了太多的答案,但事实是——startForegroundService不能在应用程序级别上被修复,你应该停止使用它。谷歌建议在调用Context# start前台服务()后5秒内使用service# start前台()API,这并不是应用程序总能做到的。

Android同时运行很多进程,并不能保证Looper会在5秒内调用你的目标服务start前台()。如果您的目标服务在5秒内没有收到调用,那么您就不走运了,您的用户将遇到ANR情况。在你的堆栈跟踪中,你会看到这样的东西:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}


main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
| sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
| state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
| stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
| held mutexes=
#00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
#01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
#02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
#03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
at android.os.MessageQueue.next (MessageQueue.java:326)
at android.os.Looper.loop (Looper.java:181)
at android.app.ActivityThread.main (ActivityThread.java:6981)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

据我所知,Looper在这里分析了队列,发现了一个“滥用者”,并简单地杀死了它。系统现在是快乐和健康的,而开发人员和用户不是,但既然谷歌限制了他们对系统的责任,他们为什么要关心后两者呢?显然他们没有。他们能做得更好吗?当然,他们本可以提供“应用程序繁忙”对话框,让用户决定是等待还是关闭应用,但为什么要麻烦呢,这不是他们的责任。最重要的是,现在的体系是健康的。

根据我的观察,这种情况发生得相对较少,在我的案例中,每个月大约有1个用户崩溃。复制它是不可能的,即使它被复制了,你也无法永久性地修复它。

在这个线程中有一个很好的建议,使用“bind”而不是“start”,然后当服务准备好时,处理onserviceconnconnected,但同样,这意味着根本不使用startForegroundService调用。

我认为,从谷歌方面正确和诚实的行动是告诉每个人startForegourndServcie有缺陷,不应该使用。

问题仍然存在:用什么来代替?幸运的是,现在有了JobScheduler和JobService,它们是前台服务的更好选择。这是一个更好的选择,因为:

当作业正在运行时,系统代表您的作业持有一个wakelock 因此,您不需要做任何保证 设备在作业期间保持清醒状态

这意味着你不再需要关心如何处理wakelocks,这就是为什么它与前台服务没有什么不同。从实现的角度来看,JobScheduler不是你的服务,它是一个系统的服务,假设它将正确地处理队列,谷歌永远不会终止自己的子服务:)

三星在其附件协议(SAP)中从startForegroundService切换到JobScheduler和JobService。当像智能手表这样的设备需要与手机这样的主机进行通信时,这是非常有用的,因为这项工作确实需要通过应用程序的主线程与用户进行交互。由于作业是由调度器发布到主线程的,所以这是可能的。不过你应该记住,作业是在主线程上运行的,并将所有繁重的工作卸载给其他线程和异步任务。

此服务在运行在您的处理器上的每个传入作业上执行 应用程序主线程。这意味着你必须卸下你的 执行逻辑到另一个线程/处理器/AsyncTask你选择

切换到JobScheduler/JobService的唯一缺陷是需要重构旧代码,这并不有趣。我花了两天时间来使用三星的新SAP实现。我会看我的坠机报告,如果再次看到坠机,我会告诉你。从理论上讲,这是不应该发生的,但总有一些细节我们可能没有意识到。

<强>更新 Play Store不再报告崩溃。这意味着JobScheduler/JobService不存在这样的问题,切换到这个模型是彻底摆脱startForegroundService问题的正确方法。我希望谷歌/Android会读到它,并最终为大家提供评论/建议/官方指导。< / p >

更新2

对于使用SAP的用户和询问SAP V2如何利用JobService的解释如下。

在你的自定义代码中,你需要初始化SAP(它是Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext,
MessageJobs::class.java!!.getName(), mAgentCallback)

现在你需要反编译三星的代码,看看里面发生了什么。在SAAgentV2中,看一下requestAgent实现和下面这行代码:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);


where d defined as below


private SAAdapter d;

现在转到SAAdapter类,并找到onServiceConnectionRequested函数,该函数使用以下调用调度作业:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12);

SAJobService只是Android'd JobService的一个实现,这是一个作业调度:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
ComponentName var7 = new ComponentName(var0, SAJobService.class);
Builder var10;
(var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
PersistableBundle var8;
(var8 = new PersistableBundle()).putString("action", var1);
var8.putString("agentImplclass", var2);
var8.putLong("transactionId", var3);
var8.putString("agentId", var5);
if (var6 == null) {
var8.putStringArray("peerAgent", (String[])null);
} else {
List var9;
String[] var11 = new String[(var9 = var6.d()).size()];
var11 = (String[])var9.toArray(var11);
var8.putStringArray("peerAgent", var11);
}


var10.setExtras(var8);
((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

如你所见,这里的最后一行使用Android'd JobScheduler来获取这个系统服务并调度作业。

在requestAgent调用中,我们传递了mAgentCallback,这是一个回调函数,它将在重要事件发生时接收控制。这是如何定义回调在我的应用程序:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
override fun onAgentAvailable(agent: SAAgentV2) {
mMessageService = agent as? MessageJobs
App.d(Accounts.TAG, "Agent " + agent)
}


override fun onError(errorCode: Int, message: String) {
App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
}
}

MessageJobs是我实现的一个类,用于处理来自三星智能手表的所有请求。这不是完整的代码,只是一个骨架:

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {




public fun release () {


}




override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
super.onServiceConnectionResponse(p0, p1, p2)
App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)




}


override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
super.onAuthenticationResponse(p0, p1, p2)
App.d(TAG, "Auth " + p1.toString())


}




override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {




}
}


override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
}


override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
super.onError(peerAgent, errorMessage, errorCode)
}


override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {


}


}

如您所见,MessageJobs也需要MessageSocket类,您需要实现这个类并处理来自设备的所有消息。

底线,它不是那么简单,它需要一些挖掘内部和编码,但它工作,最重要的是-它不会崩溃。

好吧,我注意到的一些东西可能也会对其他人有所帮助。这是严格的测试,看看我是否能找出如何解决我所看到的事件。为了简单起见,假设我有一个从演示者调用这个的方法。

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));


try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}

这将导致同样的错误。在方法完成之前,服务不会启动,因此服务中没有onCreate()

因此,即使你在主线程之外更新UI,如果你有任何东西可能会在它之后保持该方法,它不会按时启动,并给你可怕的前台错误。在我的例子中,我们将一些东西加载到一个队列中,每一个都被称为startForegroundService,但在后台每一个都涉及一些逻辑。如果逻辑花了太长时间来完成那个方法因为它们是背靠背调用的,崩溃时间。旧的startService只是忽略它,继续它的方式,因为我们每次调用它,下一轮将结束。

这让我想知道,如果我从后台线程调用服务,它是否可以在启动时完全绑定并立即运行,因此我开始试验。即使这样不会立即启动,它也不会崩溃。

new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
context.startForegroundService(new Intent(context,
TaskQueueExecutorService.class));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

我不会假装知道为什么它没有崩溃,尽管我怀疑这迫使它等待,直到主线程能够及时处理它。我知道将它绑定到主线程并不理想,但由于我的使用是在后台调用它,所以我并不真正关心它是否等待完成而不是崩溃。

我在@humazed答案中添加了一些代码。所以没有初始通知。这可能是个变通办法,但对我来说很管用。

@Override
public void onCreate() {
super.onCreate();


if (Build.VERSION.SDK_INT >= 26) {
String CHANNEL_ID = "my_channel_01";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT);


((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);


Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("")
.setContentText("")
.setColor(ContextCompat.getColor(this, R.color.transparentColor))
.setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();


startForeground(1, notification);
}
}

我在小图标和通知上添加了transparentColor。

因为每个来这里的人都遭受着同样的痛苦,我想分享我的解决方法,之前没有人尝试过(在这个问题中)。我可以向你保证,它是工作的即使在停止的断点上,这证实了这个方法。

问题是从服务本身调用Service.startForeground(id, notification),对吗?不幸的是,Android框架不保证在5秒内Service.onCreate()调用Service.startForeground(id, notification),但无论如何都会抛出异常,所以我想出了这种方法。

  1. 在调用Context.startForegroundService()之前,使用服务中的绑定器将服务绑定到上下文
  2. 如果绑定成功,调用Context.startForegroundService() 从服务连接,并立即调用Service.startForeground() 在服务连接内部。
  3. 重要提示:一个try - catch中调用Context.bindService()方法,因为在某些情况下,调用会抛出异常,在这种情况下,你需要直接调用Context.startForegroundService(),并希望它不会失败。一个例子可以是广播接收器上下文,但是在这种情况下,获取应用程序上下文并不会抛出异常,而是直接使用上下文。

这甚至可以在我绑定服务后和触发“startForeground”调用之前等待断点时工作。等待3-4秒不会触发异常,而5秒后会抛出异常。(如果设备不能在5秒内执行两行代码,那么就该把它扔进垃圾桶了。)

因此,首先创建一个服务连接。

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
// The binder of the service that returns the instance that is created.
MyService.LocalBinder binder = (MyService.LocalBinder) service;


// The getter method to acquire the service.
MyService myService = binder.getService();


// getServiceIntent(context) returns the relative service intent
context.startForegroundService(getServiceIntent(context));


// This is the key: Without waiting Android Framework to call this method
// inside Service.onCreate(), immediately call here to post the notification.
myService.startForeground(myNotificationId, MyService.getNotification());


// Release the connection to prevent leaks.
context.unbindService(this);
}


@Override
public void onBindingDied(ComponentName name)
{
Log.w(TAG, "Binding has dead.");
}


@Override
public void onNullBinding(ComponentName name)
{
Log.w(TAG, "Bind was null.");
}


@Override
public void onServiceDisconnected(ComponentName name)
{
Log.w(TAG, "Service is disconnected..");
}
};

在服务内部,创建一个返回服务实例的绑定器。

public class MyService extends Service
{
public class LocalBinder extends Binder
{
public MyService getService()
{
return MyService.this;
}
}


// Create the instance on the service.
private final LocalBinder binder = new LocalBinder();


// Return this instance from onBind method.
// You may also return new LocalBinder() which is
// basically the same thing.
@Nullable
@Override
public IBinder onBind(Intent intent)
{
return binder;
}
}

然后,从该上下文中尝试绑定服务。如果成功,它将从您正在使用的服务连接调用ServiceConnection.onServiceConnected()方法。然后,处理上面所示代码中的逻辑。示例代码如下所示:

// Try to bind the service
try
{
context.bindService(getServiceIntent(context), connection,
Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
// This is probably a broadcast receiver context even though we are calling getApplicationContext().
// Just call startForegroundService instead since we cannot bind a service to a
// broadcast receiver context. The service also have to call startForeground in
// this case.
context.startForegroundService(getServiceIntent(context));
}

它似乎在我开发的应用程序上工作,所以当你尝试时它应该也能工作。

当使用目标sdk 28或更高版本时,你必须为android 9设备添加如下权限,否则异常总是会发生:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

我只是分享我对这个的评论。我不确定(100%告诉)上面的代码对我和其他人也不起作用,但有时我会遇到这个问题。假设我运行应用程序10次,那么这个问题可能会得到2到3次

我尝试了所有的答案,但仍然没有解决这个问题。我已经实现了以上所有代码,并在不同的api级别(api级别26,28,29)和不同的移动设备(三星,小米,MIUI, Vivo, Moto, One Plus,华为等)上进行了测试,并得到了相同的以下问题。

Context.startForegroundService() did not then call Service.startForeground();

我在谷歌开发人员网站上阅读了服务,一些其他博客和一些堆栈溢出问题,并得到了这个问题将发生在我们调用startForgroundSerivce()方法时,但当时服务没有启动的想法。

在我的情况下,我已经停止服务后立即启动服务。下面是提示。

....//some other code
...// API level and other device auto star service condition is already set
stopService();
startService();
.....//some other code

在这种情况下,由于处理速度和RAM中的内存不足,服务不会启动,但会调用startForegroundService()方法并触发异常。

Work for me:

new Handler().postDelayed(()->ContextCompat.startForegroundService(activity, new Intent(activity, ChatService.class)), 500);

我改变了代码,并设置了500毫秒的延迟来调用startService()方法,问题解决了。这不是完美的解决方案,因为这样应用程序的性能会下降。

< p > Note: 这仅适用于前景和背景服务。使用Bind服务时没有测试。 我分享这一点,因为只有这是我解决这个问题的方法
一个问题可能是服务类没有在AndroidManifest文件中启用。

<service
android:name=".AudioRecorderService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="microphone" />

我在Pixel 3和Android 11中遇到了一个问题,当我的服务运行得很短时,前台通知没有被取消。

在stopForeground() stopSelf()之前添加100ms的延迟似乎有所帮助

人们在这里写道,stopprospect()应该在stopSelf()之前被调用。我不能确认,但我猜它不会费心去做。

public class AService extends Service {


@Override
public void onCreate() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
getForegroundNotificationId(),
channelManager.buildBackgroundInfoNotification(getNotificationTitle(), getNotificationText()),
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
} else {
startForeground(getForegroundNotificationId(),
channelManager.buildBackgroundInfoNotification(getNotificationTitle(), getNotificationText())
);
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground();


if (hasQueueMoreItems()) {
startWorkerThreads();
} else {
stopForeground(true);
stopSelf();
}
return START_STICKY;
}


private class WorkerRunnable implements Runnable {


@Override
public void run() {


while (getItem() != null && !isLoopInterrupted) {
doSomething(getItem())
}


waitALittle();
stopForeground(true);
stopSelf();
}


private void waitALittle() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

从谷歌的文档Android 12行为改变:

To provide a streamlined experience for short-running foreground services on Android 12, the system can delay the display of foreground service notifications by 10 seconds for certain foreground services. This change gives short-lived tasks a chance to complete before their notifications appear.

解决方案:在onCreate()中为你使用的服务调用start前台()

在我的情况下,通知ID我传递给startForeground方法是'0',因为这个错误来了。

startForeground(0, notification); //This is wrong.


startForeground(1, notification); //This is right.

可以使用0以外的任何整数。

大约有10个用户在我们的应用程序的崩溃分析中得到这个错误。

enter image description here

正如Kimi Chiu回答的那样:这个问题的主要原因是服务在提升到前台之前就停止了。但是断言在服务被销毁后并没有停止。你可以尝试在调用startforegroundservice后添加StopService来重现这个过程

所以我测试了这个,并得以复制。

我应用的一个解决方案是,我让服务至少停留5秒钟,这样服务就会提升到前台。现在我在测试时无法重现这个问题。

private fun stopService() {


lifecycleScope.launch {
delay(5000L)
            

try {
stopForeground(true)
isForeGroundService = false
stopSelf()
} catch (e: Exception) {
e.printStackTrace()
}
}
}

让我们看看这个问题是否在我们的下一个构建中重现。

更新:)→这次没有与Context.startForegroundService()相关的问题,然后没有调用service . start前台()

enter image description here

之前/之后comparission→

Before->enter image description here enter image description here < / p >

后→ enter image description here

在我的例子中,我称之为上下文。在服务有机会在内部调用start前台之前,停止服务(外部服务)。