然后Context.startForegroundService()没有调用Service.startForeground()

问题描述 投票:162回答:27

我在Android O OS上使用Service类。

我计划在后台使用Service

Android建议指出startService()应该使用startForegroundService()

如果你使用startForegroundService()Service会抛出

然后Context.startForegroundService()没有调用Service.startForeground()

错误。

这有什么问题?

android service operating-system foreground android-8.0-oreo
27个回答
81
投票

来自谷歌关于Android 8.0 behavior changes的文档:

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

解决方案:在startForeground()中调用onCreate()Service使用Context.startForegroundService()

另见:Background Execution Limits for Android 8.0(Oreo)


4
投票

这么多回答,但没有一个在我的情况下有效。

我已经开始这样的服务了。

              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();

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


3
投票

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

与startService(Intent)类似,但是一旦开始运行,服务将调用startForeground(int,android.app.Notification)的隐式承诺。为此提供服务的时间与ANR间隔相当,否则系统将自动停止服务并声明应用程序ANR。

与普通的startService(Intent)不同,此方法可以随时使用,无论托管服务的应用程序是否处于前台状态。

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

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


3
投票

我面临同样的问题,花了很长时间找到解决方案,你可以尝试下面的代码。如果您使用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);
    }

3
投票

Android O API问题26

如果您立即停止服务(因此您的服务实际上并未真正运行(措辞/理解)并且您处于ANR间隔之下,您仍需要在stopSelf之前调用startForeground

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

尝试过这种方法但它仍然会产生错误: -

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

我正在使用它,直到错误得到解决

mContext.startService(playIntent);

3
投票

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

my foreground service.Java

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

main activity.Java

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

代码的以下块抛出异常:

active services.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);
         }
    }
    ...
}

此方法在onCreate()MyForegroundService之前执行,因为Android在主线程处理程序上调度服务的创建,但bringDownServiceLockedBinderThread上调用,这是一种竞争条件。这意味着MyForegroundService没有机会打电话给startForeground,这将导致崩溃。

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

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));
    }
}

通过使用粘性广播,我们确保广播不会丢失,并且stopReceiver一旦在onCreate()MyForegroundService中注册就会收到停止意图。到这时我们已经打电话给startForeground(...)。我们还必须删除该粘性广播,以防止下次通知stopReceiver。

请注意,方法sendStickyBroadcast已弃用,我仅将其用作解决此问题的临时解决方法。


3
投票

请不要在onCreate()方法中调用任何StartForgroundServices,你必须在创建工作线程后调用onStartCommand()中的StartForground服务,否则你总是得到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(0, 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(0, notification);
            Log.e("home_button_value","home_button_value");

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

    }
}

编辑:小心! startForeground函数不能将0作为第一个参数,它会引发异常!此示例包含错误的函数调用,将0更改为您自己的const,该const不能为0或大于Max(Int32)


1
投票

甚至在startForeground中调用Service之后,如果我们在调用stopService之前调用onCreate,它会在某些设备上崩溃。所以,我通过使用另一个标志启动服务来修复此问题:

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
    boolean stopService = false;
    if (intent != null) {
        stopService = intent.getBooleanExtra("request_stop", false);
    }
    if (stopService) {
        stopSelf();
        return START_STICKY;
    }
    //Continue with the background task
    return START_STICKY;
}

附:如果服务实际上没有运行,它将首先启动服务,这是一个开销。


1
投票

我只是在调用PendingIntent函数之前检查context.startForegroundService(service_intent)是否为null。

这对我有用

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);
        }
}

1
投票

在创建Service或IntentService之后立即调用startForeground方法。像这样:

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

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

1
投票

我知道,已经发布了太多的答案,但事实是 - startForegroundService无法在应用程序级别修复,您应该停止使用它。谷歌建议在调用Context#startForegroundService()后5秒内使用Service#startForeground()API,这不是应用程序可以随时执行的操作。

Android同时运行很多进程,并且没有任何保证Looper将在5秒内调用应该调用startForeground()的目标服务。如果您的目标服务在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在这里分析了队列,找到了一个“滥用者”并简单地杀了它。现在系统既快乐又健康,而开发人员和用户却没有,但由于Google限制了他们对系统的责任,他们为什么要关心后两者呢?显然他们没有。他们可以做得更好吗?当然,例如他们可以服务“应用程序忙”对话框,要求用户做出关于等待或杀死应用程序的决定,但为什么要打扰,这不是他们的责任。主要的是现在系统健康。

根据我的观察,这种情况相对较少发生,在我的情况下,1K用户在一个月内大约发生1次崩溃。再现它是不可能的,即使它被复制,你也无法永久地修复它。

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

我认为,谷歌方面的正确和诚实的行动是告诉大家startForegroundService有一个缺点,不应该使用。

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

当作业正在运行时,系统会代表您的应用程序保留唤醒锁。因此,您无需采取任何措施来保证设备在工作期间保持清醒状态。

这意味着您不再需要关心处理唤醒锁了,这就是它与前台服务没有区别的原因。从实现PoV JobScheduler不是你的服务,它是一个系统的,可能它会正确处理队列,谷歌永远不会终止自己的孩子:)

三星已在其三星附件协议(SAP)中从startForegroundService切换到JobScheduler和JobService。当智能手表等设备需要与手机等主机通信时,它非常有用,这些主机需要通过应用程序的主线程与用户进行交互。由于作业由调度程序发布到主线程,因此可以实现。您应该记住,该作业正在主线程上运行,并将所有繁重的内容卸载到其他线程和异步任务。

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

切换到JobScheduler / JobService的唯一缺陷是你需要重构旧代码,这并不好玩。我花了两天时间才使用新的三星SAP实施。我会看看我的崩溃报告,如果再次看到崩溃,请告诉您。理论上它不应该发生,但总有一些我们可能不知道的细节。

更新Play商店不再报告崩溃事件。这意味着JobScheduler / JobService没有这样的问题,切换到这个模型是一次彻底摆脱startForegroundService问题的正确方法。我希望谷歌/安卓可以阅读它,并最终评论/建议/为每个人提供官方指导。


61
投票

我打电话给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);
        }
}

0
投票

我知道这是一个迟到的答案但是,我认为它将来会有所帮助,我只是使用JobIntentService而不是IntentService,包括它的JobScheduler并为我处理一切。看看这个example


0
投票

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


0
投票

Updating Data in onStartCommand(...)

onBind(...)

onBind(...)是一个更好的生命周期事件,以启动startForegroundonCreate(...),因为onBind(...)通过Intent可能包含初始化Bundle所需的Service中的重要数据。然而,当onStartCommand(...)第一次被创建或之后被称为Service时,没有必要调用startForeground

onStartCommand(...)

onStartCommand(...)中的Service对于更新ContextCompat.startForegroundService(...)一直很重要。

ServiceonBind(...)创建之后被调用时,onCreate(...)onStartCommand(...)不会被调用。因此,更新的数据可以通过Intent Bundle传递到Service,以更新PlayerNotificationManager中的数据。

样品

我正在使用这种模式在Coinverse加密货币新闻应用程序中实现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() }) })

Activity / Fragment.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)
}

AudioService.kt

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

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

0
投票

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

onCreate()

这将崩溃同样的错误。在方法完成之前,服务不会启动,因此服务中没有startForegroundService

因此,即使您从主线程更新UI,如果您有任何可能会阻止该方法的UI,它将无法按时启动并为您提供可怕的前景错误。在我的例子中,我们将一些东西加载到一个队列中,每个东西都被称为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(); } } }); 只是忽略它并继续它的方式,因为我们每次都调用它,下一轮将完成。

这让我想知道,如果我从后台的一个线程调用该服务,它可能不会在开始时完全绑定并立即运行,所以我开始尝试。即使这不是立即启动它,它也不会崩溃。

https://github.com/paulpv/ForegroundServiceAPI26/tree/bound

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


-1
投票

如果您重写要启动的服务w / bindService,这一切都会消失。

有关如何执行此操作的示例,请参阅:https://github.com/paulpv/ForegroundServiceAPI26/compare/repro...bound?expand=1

我的“repro”分支的差异可以在: <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> 看到


-1
投票

对我来说,我在清单中添加了前台服务

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)
        }

    }

}

如果它有效,请在下方发表评论


-1
投票

Service

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)
}

Tester

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

Result

   @Override
    public void onCreate() {
try{
}
catch(Exception e)
{
}
finally{
    startForeground(1, notificationbuilder.build());

}
    }

-6
投票

确保所有代码路径都调用startForeground方法,例如代码可能会在服务的onCreate方法上生成一个异常,阻止调用startForeground

qazxswpoi

36
投票

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

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

  1. startForeground通知必须在onCreateonStartCommand中,因为如果您的服务已经创建并且某种程度上您的活动试图再次启动它,则不会调用onCreate
  2. 通知ID不能为0,否则会发生同样的崩溃,即使原因不一样。
  3. stopSelf之前不得召唤startForeground

有了以上所有3这个问题可以减少一点但仍然不是一个修复,真正的修复或让我们说解决方法是将目标sdk版本降级到25。

请注意,很可能Android P仍会出现这个问题,因为谷歌甚至不知道发生了什么,也不相信这是他们的错,请阅读#36#56获取更多信息


18
投票

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

我在这里有一个明确的repro ForegroundServiceAPI26

我已在此处打开了一个错误:Google issue tracker

关于此的几个错误已经打开并关闭将不会修复。

希望采用明确的复制步骤进行复制。

谷歌团队提供的信息

Google issue tracker Comment 36

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

Re #31,发布其他应用程序可以直接启动的服务从根本上说是不安全的。您可以通过将该服务的所有启动操作视为需要startForeground()来缓解这一点,但显然这可能与您的想法不同。

Google issue tracker Comment 56

有几种不同的情景会导致相同的结果。

直接的语义问题,用startForegroundService()甩掉一些东西只是一个错误,而忽略了实际上通过startForeground()将它转换到前景,就是这样一个语义问题。故意将其视为app bug。在将服务转换为前台之前停止服务是应用程序错误。这是OP的关键,也就是为什么这个问题被标记为“按预期工作”。

但是,也存在关于该问题的虚假检测的问题。这被视为一个真正的问题,尽管它与这个特定的bug跟踪器问题分开跟踪。我们对投诉没有充耳不闻。


11
投票

我已经研究了几天并得到了解决方案。现在在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
    }
}

10
投票

我有一个widget,它在设备清醒时进行相对频繁的更新,我在短短几天内就看到了成千上万的崩溃事件。

问题触发器

我甚至在我的Pixel 3 XL上都注意到了这个问题,当时我根本不认为该设备有很多负载。所有代码路径都用startForeground()覆盖。但后来我意识到,在很多情况下,我的服务很快就完成了工作。我相信我的应用程序的触发器是服务在系统实际到达显示通知之前完成。

解决方法/解决方案

我能够摆脱所有崩溃。我做的是删除对stopSelf()的调用。 (我在考虑延迟停止,直到我非常确定通知已显示,但我不希望用户在没有必要的情况下看到通知。)当服务闲置一分钟或系统时通常会破坏它而不会抛出任何异常。

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

7
投票

只是一个抬头,因为我浪费了太多时间。即使我把startForeground(..)称为onCreate(..)的第一件事,我也一直得到这个例外。最后我发现问题是由使用NOTIFICATION_ID = 0引起的。使用任何其他值似乎解决了这个问题。


6
投票

我有一个解决这个问题的方法。我已经在我自己的应用程序(300K + DAU)中验证了此修复程序,这可以减少至少95%的此类崩溃,但仍然无法100%避免此问题。

即使您确保在Google记录的服务启动后立即调用startForeground(),也会出现此问题。这可能是因为在许多情况下服务创建和初始化过程已花费超过5秒,然后无论何时何地调用startForeground()方法,此崩溃都是不可避免的。

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

  1. 首先不要使用startForegroundService,请将bindService()与auto_create标志一起使用。它将等待服务初始化。这是代码,我的示例服务是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. 然后是MusicBinder实现: /** * 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. 最重要的部分,MusicService实现,forceForeground()方法将确保在startForegroundService()之后调用startForeground()方法: 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. 如果要在挂起的意图中运行第1步代码段,例如,如果要在窗口小部件中启动前台服务(单击窗口小部件按钮)而不打开应用程序,则可以将代码段包装在广播接收器中,并触发广播事件而不是启动服务命令。

就这些。希望能帮助到你。祝好运。


5
投票

当id设置为0时调用Service.startForeground(int id, Notification notification)时,Android 8+上也会出现此错误。

id int:根据NotificationManager.notify(int,Notification)的此通知的标识符;一定不能是0。

© www.soinside.com 2019 - 2024. All rights reserved.