Android 14下ForegroundServiceDidNotStartInTimeException崩溃求助
前台定位服务Android 14崩溃排查与解决
问题背景
开发了带通知的前台定位服务用于追踪用户位置,部分Android 14用户出现崩溃,无法复现该问题,Crashlytics捕获到ForegroundServiceDidNotStartInTimeException异常。
崩溃日志
Fatal Exception: android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{2170719 u0 com.anstar.fieldworkhq/.coordinates.ServiceTechnicianTrackingService} at android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException(ActivityThread.java:2315) at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2286) Caused by android.app.StackTrace: Last startServiceCommon() call for this service was made here at android.app.ContextImpl.startServiceCommon(ContextImpl.java:2023) at android.app.ContextImpl.startForegroundService(ContextImpl.java:1967) at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:847) at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:847) at com.zoran.app.main.MainActivity$$ExternalSyntheticApiModelOutline0.m(D8$$SyntheticClass) at com.zoran.app.main.MainActivity.startForegroundService(MainActivity.java:326) at com.zoran.app.main.MainActivity.tryToStartForegroundLocationService(MainActivity.java:313) at com.zoran.app.main.MainActivity.trackLocationIfNeeded(MainActivity.java:220) at com.zoran.app.main.MainActivity.onCreate(MainActivity.java:95) at android.app.Activity.performCreate(Activity.java:8975) at android.app.Activity.performCreate(Activity.java:8944)
相关启动逻辑代码
private void trackLocationIfNeeded() { if (presenter.isTrackable()) { if (ViewUtil.isPlayServiceAvailable(this)) { if (isForegroundLocationPermissionGranted() && presenter.isTrackTechnicianRouteAllowed()) { tryToStartForegroundLocationService(); presenter.saveThatUserAskedForPermission(); } else { if (!presenter.isUserAskedForLocationPermission()) { askForLocationPermission(); presenter.saveThatUserAskedForPermission(); } } } } else { stopServiceTechnicianLocationService(); } } private void tryToStartForegroundLocationService() { if(Utils.isPlayServiceAvailable(this)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if(isPostNotificationsPermissionGranted()) { startForegroundService(); } else { askForPostNotificationsPermission(); } } else { startForegroundService(); } } } private void startForegroundService() { Intent i = new Intent(this, ServiceTechnicianTrackingService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(i); } else { startService(i); } }
前台服务代码(ServiceTechnicianTrackingService)
public class ServiceTechnicianTrackingService extends Service { private FusedLocationProviderClient fusedLocationProviderClient; private LocationCallback locationCallback; @Inject ServiceTechnicianLocationManager serviceTechnicianCoordinateManager; @Inject NetworkManager NetworkManager; public static final String ACTION_STOP_SERVICE = "action_stop_service"; private boolean isStarted = false; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); injector().inject(this); fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this); locationCallback = new LocationCallback() { @Override public void onLocationResult(@NotNull LocationResult locationResult) { if(locationResult != null && locationResult.getLastLocation() != null) { super.onLocationResult(locationResult); String lat = String.valueOf(locationResult.getLastLocation().getLatitude()); String lng = String.valueOf(locationResult.getLastLocation().getLongitude()); Timber.d("Lat is: " + lat + " lng is: " + lng); if(networkManager.isNetworkAvailable()) { serviceTechnicianCoordinateManager.updateCurrentLocation(new Coordinate(lat, lng)); } } } }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // Call startForeground early to prevent the exception if(!isStarted) { startForegroundAndDisplayNotification(); isStarted = true; } if(intent != null) { String action = intent.getAction(); if(ACTION_STOP_SERVICE.equalsIgnoreCase(action)) { stopForeground(true); stopSelf(); } else { requestLocationUpdates(); } } return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); stopLocationUpdates(); isStarted = false; } private void startForegroundAndDisplayNotification() { if (Build.VERSION.SDK_INT >= 26) { try { Notification notification = createNotification(); startForeground(1, notification); } catch (Exception e) { Timber.e(e, "Exception in startForegroundAndDisplayNotification"); } } }
AndroidManifest配置
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <service android:name=".coordinates.ServiceTechnicianTrackingService" android:enabled="true" android:exported="false" android:foregroundServiceType="location" />
崩溃原因分析
- Android 14超时限制收紧:Android 14要求调用
startForegroundService()后必须在5秒内调用startForeground(),超时就会触发该异常,相比旧版本时间要求更严格。 - 服务启动流程延迟:
onCreate()中执行依赖注入、初始化FusedLocationProviderClient等操作,若这些步骤耗时,会挤占onStartCommand()中调用startForeground()的时间窗口。 - 通知创建潜在问题:
createNotification()可能存在未捕获的异常或耗时操作(如创建通知渠道、加载资源慢),导致startForeground()未被成功执行;即使有try-catch,异常后服务仍处于未启动前台的非法状态。 - 重复启动隐患:若服务已启动,再次调用
startForegroundService()会触发onStartCommand(),但此时isStarted为true,不会调用startForeground(),Android 14系统仍可能判定未及时启动前台而抛出异常。
解决方案
1. 前置前台启动,减少初始化延迟
将startForeground()调用移到onCreate()最开头,优先完成前台启动,再执行其他初始化操作:
@Override public void onCreate() { super.onCreate(); // 优先启动前台,抢占时间窗口 startForegroundAndDisplayNotification(); isStarted = true; // 后续初始化操作 injector().inject(this); fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this); locationCallback = new LocationCallback() { // 原有逻辑 }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 移除isStarted判断,前台已在onCreate中启动 if(intent != null) { String action = intent.getAction(); if(ACTION_STOP_SERVICE.equalsIgnoreCase(action)) { stopForeground(true); stopSelf(); } else { requestLocationUpdates(); } } return START_STICKY; }
2. 优化通知创建逻辑
- 在Application类中提前创建通知渠道,避免服务启动时创建渠道的延迟。
- 简化通知布局,避免加载大量资源;增加通知非空判断,创建失败时主动停止服务:
private void startForegroundAndDisplayNotification() { if (Build.VERSION.SDK_INT >= 26) { Notification notification = createNotification(); if(notification == null) { stopSelf(); return; } try { startForeground(1, notification); } catch (Exception e) { Timber.e(e, "Failed to start foreground"); stopSelf(); } } }
3. 避免重复启动服务
增加服务运行状态检查,防止重复调用startForegroundService():
private boolean isServiceRunning() { ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (ServiceTechnicianTrackingService.class.getName().equals(service.service.getClassName())) { return true; } } return false; } private void startForegroundService() { if(isServiceRunning()) { return; } Intent i = new Intent(this, ServiceTechnicianTrackingService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(i); } else { startService(i); } }
4. Android 14权限适配
确保POST_NOTIFICATIONS权限申请流程无漏洞,Android 14中无该权限时startForeground()会直接失败,进而触发超时异常。
内容的提问来源于stack exchange,提问作者Zookey
相关产品推荐
相关产品推荐

