关于Context.startForegroundService() did not then call Service.startForeground()的解决办法

  • 有天突然报了以下错误
  • android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground() 在这里插入图片描述
  • 顺着这个提示文本,我搜索了下,发现在com.android.server.am.ActiveServices里有两处地方

第一处:停止服务

  • 第一处如下
//android-30
 void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) {
        mAm.crashApplicationWithType(app.uid, app.getPid(), app.info.packageName, app.userId,
                "Context.startForegroundService() did not then call Service.startForeground(): "
                    + serviceRecord, false /*force*/,
                ForegroundServiceDidNotStartInTimeException.TYPE_ID);
    }
  • 看看是谁调用它的,原来在com.android.server.am.ActivityManagerService里

  • 在这里插入图片描述

  • 那就接着看看什么时候发出了SERVICE_FOREGROUND_CRASH_MSG事件,于是又来到ActiveServices.bringDownServiceLocked方法里

  • 在这里插入图片描述

  • 根据以下这行注释大概意思就是说在 显示前台服务通知之前就关闭前台服务这个操作是不允许的

  • 在这里插入图片描述

  • 那么就有可能是我们启动前台服务,然后前台服务还没执行到android.app.Service#startForeground(int, android.app.Notification) 时就又执行了停止服务,因此才出现了这个奔溃

  • 于是我们可以验证下在startForeground调用之后立马执行stopSelf,看看会不会报同样的错

  • 可惜无法复现,再看看产生这个奔溃的手机,全是android 10这个版本

  • 于是对android 10的设备一番尝试后发现无法复现,那么可能产生的问题就是超时了

  • 后期发现如果是这里产生的奔溃,那么异常是ForegroundServiceDidNotStartInTimeException,如下

android.app.ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{...}
  • 而且从代码看在这个异常产生之前就会把超时的那个消息从消息队列移除,也就是不会产生两次异常
  • 在这里插入图片描述
  • 后来继续思考,会不会是未调用startForeground就先调用了stopSelf导致的呢,这种情况应该也有可能,马上着手试验,果然立马就崩了

第二处:超时

  • 第二处如下
android-30
void serviceForegroundTimeout(ServiceRecord r) {
        ProcessRecord app;
        synchronized (mAm) {
            if (!r.fgRequired || r.destroying) {
                return;
            }

            app = r.app;
            if (app != null && app.isDebugging()) {
                // The app's being debugged; let it ride
                return;
            }

            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "Service foreground-required timeout for " + r);
            }
            r.fgWaiting = false;
            stopServiceLocked(r, false);
        }

        if (app != null) {
            mAm.mAnrHelper.appNotResponding(app,
                    "Context.startForegroundService() did not then call Service.startForeground(): "
                        + r);
        }
    }
  • 这个看起来是超时的时候会报anr,那么多久才算超时呢
//android-30
public final class ActiveServices {
    
    // How long the startForegroundService() grace period is to get around to
    // calling startForeground() before we ANR + stop it.
    static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;
}
  • 从这里就可以看到超时时间是10秒(这个时间可能会随着不同sdk版本而变化)
  • 接着继续跟踪可以看到com.android.server.am.ActivityManagerService的事件SERVICE_FOREGROUND_TIMEOUT_MSG调用到了serviceForegroundTimeout
  • 在这里插入图片描述
  • 接下来可以看到只有这一处产生了这个事件
//android-30 com.android.server.am.ActiveServices#scheduleServiceForegroundTransitionTimeoutLocked
 void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
        if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
            return;
        }
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
        msg.obj = r;
        r.fgWaiting = true;
        mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);
    }
  • 那么我们可以在服务的android.app.Service.onCreate方法里模拟一下超时
public class XxxService extends Service {
 	@Override
    public void onCreate() {
        super.onCreate();
    	try {
            Thread.sleep(11000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 结果真的只在android10的设备上再次复现

结论

  • 如果使用startForegroundService方法,就一定要保证在服务停止前调用startForeground方法,否则就使用普通的startService方法即可(startService+startForeground也可以产生前台服务效果)
  • 不要在调用startForeground后立马执行stopself,有概率会产生奔溃
  • 不要在服务的onCreate等方法中执行耗时操作

另外,根据我的实验和网上的资料,这个问题在android10上出现得比较多(另外,小米的推送sdk在android10上也经常产生这个奔溃:android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{96005dd u0 com.xxx.xxx/com.xiaomi.mipush.sdk.PushMessageHandler}),所以很有理由怀疑是android10系统导致,像android11即使模拟超时10秒,也不会产生此奔溃

参考

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐