iOS 13后台服务挂起:remote silent notification唤醒传位置方案咨询
iOS 13 后台静默推送唤醒发送位置的解决方案
我之前也碰到过一模一样的问题,iOS 13 确实收紧了后台唤醒的限制,但不用 BackgroundTasks API 也还是有办法实现需求的——核心是靠后台任务延长执行时间配合正确的静默推送配置,下面是具体的步骤和注意事项:
一、确保静默推送的 Payload 配置正确
首先你的推送 payload 必须严格符合静默推送的要求,否则系统根本不会唤醒后台的 App:
- 必须包含
aps字典里的content-available: 1 - 不能包含
alert、badge、sound这些会触发用户通知的字段(如果一定要加 sound,得设为空字符串"",但非常不建议这么做)
示例 payload:
{ "aps": { "content-available": 1 }, "action": "send_location" // 自定义字段,用来标识这是触发位置发送的推送 }
二、用后台任务延长执行窗口
iOS 13 之后,didReceiveRemoteNotification:fetchCompletionHandler: 方法的后台执行时间被大幅压缩(大概只有几秒),直接在里面做定位和网络请求很容易被系统终止。所以必须通过 beginBackgroundTaskWithName:expirationHandler: 来申请额外的后台执行时间(最多约30秒)。
代码实现示例(Objective-C)
首先在 AppDelegate 里处理推送回调:
// 声明属性保存后台任务ID和completionHandler @property (nonatomic, assign) UIBackgroundTaskIdentifier bgTask; @property (nonatomic, copy) void (^fetchCompletionHandler)(UIBackgroundFetchResult); - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // 保存completionHandler,后续在定位/网络请求完成后调用 self.fetchCompletionHandler = completionHandler; // 启动后台任务 self.bgTask = [application beginBackgroundTaskWithName:@"SendLocationTask" expirationHandler:^{ // 任务超时,必须结束后台任务,否则App会被系统杀死 [application endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; if (self.fetchCompletionHandler) { self.fetchCompletionHandler(UIBackgroundFetchResultFailed); self.fetchCompletionHandler = nil; } }]; // 触发单次定位请求(比持续定位更高效,节省电量和时间) CLLocationManager *locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; // 确保已经获取了后台定位权限 if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways) { [locationManager requestLocation]; } else { // 没有权限的情况,结束任务并返回失败 [application endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; completionHandler(UIBackgroundFetchResultFailed); } }
然后实现 CLLocationManager 的代理方法,处理定位结果并发送请求:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations { if (locations.count == 0) { [self finishBackgroundTaskWithResult:UIBackgroundFetchResultFailed]; return; } CLLocation *currentLoc = locations.lastObject; // 构造位置数据并发送到服务器 NSDictionary *postData = @{ @"lat": @(currentLoc.coordinate.latitude), @"lon": @(currentLoc.coordinate.longitude), @"device_id": [[UIDevice currentDevice] identifierForVendor].UUIDString }; NSURL *url = [NSURL URLWithString:@"https://your-server-endpoint.com/upload-location"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = [NSJSONSerialization dataWithJSONObject:postData options:0 error:nil]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { UIBackgroundFetchResult result = error ? UIBackgroundFetchResultFailed : UIBackgroundFetchResultNewData; [self finishBackgroundTaskWithResult:result]; }]; [task resume]; [manager stopUpdatingLocation]; } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { [self finishBackgroundTaskWithResult:UIBackgroundFetchResultFailed]; } // 封装结束后台任务的方法 - (void)finishBackgroundTaskWithResult:(UIBackgroundFetchResult)result { if (self.bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; } if (self.fetchCompletionHandler) { self.fetchCompletionHandler(result); self.fetchCompletionHandler = nil; } }
三、关键权限配置
定位权限:必须申请始终允许的定位权限,在 Info.plist 中添加:
NSLocationAlwaysAndWhenInUseUsageDescription:描述为什么需要始终定位NSLocationWhenInUseUsageDescription:备用描述(适配旧系统)
代码中要调用[locationManager requestAlwaysAuthorization]来请求权限。
后台模式:在 Xcode 的项目配置 -> Signing & Capabilities -> Background Modes 中,勾选
Remote notifications和Location updates(因为需要后台定位)。
注意事项
- 系统给的后台任务时间有限(约30秒),所以要尽量优化操作:用单次定位
requestLocation代替持续定位,网络请求尽量简洁,避免不必要的计算。 - 如果 App 被用户手动杀死(从多任务划掉),静默推送无法唤醒 App,这是 iOS 的系统限制,无法绕过。
- 虽然这种方式比 BackgroundTasks 更可控,但系统仍可能因资源紧张终止后台任务,所以要做好失败后的重试逻辑(比如下次启动时补发)。
内容的提问来源于stack exchange,提问作者Preema
相关产品推荐
相关产品推荐

