iOS平台Unity应用后台采集指南针数据的问题咨询
问题描述
我Objective-C和Swift基础薄弱,耗时编写了一款iOS原生插件,用于Unity应用进入后台时通过后台线程采集指南针数据。插件能编译运行,但仅能持续约2秒就被系统终止。我希望实现长时间采集指南针数据,或者至少获取带时间戳的指南针数据,想请教代码存在的问题、可行实现方案及相关参考方向。
相关代码
iOS原生插件代码(MyPlugin.m)
extern "C" { #import <CoreLocation/CoreLocation.h> #import "MyPlugin.h" @implementation MyPlugin NSThread *_magneticHeadThread; BOOL _shouldStop; NSMutableArray *readings; - (void)startBackgroundThread { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSLog(@"Background thread started!"); readings = [NSMutableArray array]; _magneticHeadThread = [[NSThread alloc] initWithTarget:self selector:@selector(retrieveMagneticHeadPosition) object:nil]; [_magneticHeadThread start]; }); } - (void)applicationDidEnterBackground:(NSNotification *)notification { _shouldStop = NO; [self startBackgroundThread]; } - (void)applicationWillEnterForeground:(NSNotification *)notification { _shouldStop = YES; [_magneticHeadThread cancel]; NSLog(@"Magnetic head position: %lu", [readings count]); } + (instancetype)sharedInstance { static MyPlugin *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[MyPlugin alloc] init]; }); return sharedInstance; } - (void)retrieveMagneticHeadPosition { CLLocationManager *locationManager = [[CLLocationManager alloc] init]; locationManager.headingFilter = kCLHeadingFilterNone; [locationManager startUpdatingHeading]; int c = 0; while (!_shouldStop) { NSLog(@"Magnetic head position: %i", c); c++; [readings addObject:@(locationManager.heading.magneticHeading)]; [NSThread sleepForTimeInterval:0.1]; } [locationManager stopUpdatingHeading]; } @end void UnityMyPlugin_StartBackgroundThread() { [[NSNotificationCenter defaultCenter] addObserver:[MyPlugin sharedInstance] selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:[MyPlugin sharedInstance] selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; } }
Unity端调用代码
void Start() { Application.runInBackground = true; #if UNITY_IPHONE && !UNITY_EDITOR UnityMyPlugin_StartBackgroundThread(); #endif }
代码存在的问题
- iOS后台执行限制未处理:iOS应用进入后台后,默认仅保留2-10秒的后台执行窗口,你的代码未申请对应后台权限,系统会直接终止后台线程。
- CLLocationManager使用错误:
- 采用主动轮询
locationManager.heading的方式,违背了CLLocationManager通过代理回调获取数据的设计逻辑,无法准确捕获实时更新。 locationManager是局部变量,方法执行完毕后可能被提前释放,导致后续无法获取数据。
- 采用主动轮询
- 线程安全隐患:全局变量
_shouldStop未加锁保护,多线程环境下可能出现读写不一致的问题。 - 线程嵌套冗余:在全局队列异步后再启动新
NSThread,属于不必要的线程嵌套,增加了代码复杂度。
可行实现方案
方案一:申请iOS后台定位权限(实现长时间采集)
iOS允许开启后台定位更新的应用在后台持续获取航向数据,步骤如下:
- 在
Info.plist中添加权限描述:NSLocationWhenInUseUsageDescription:前台使用定位的说明文本NSLocationAlwaysAndWhenInUseUsageDescription:前后台使用定位的说明文本
- 修改插件代码,改用代理回调并配置后台权限:
@interface MyPlugin () <CLLocationManagerDelegate> @property (nonatomic, strong) CLLocationManager *locationManager; @property (nonatomic, assign) UIBackgroundTaskIdentifier bgTask; @end @implementation MyPlugin // 单例等原有代码保留 - (void)applicationDidEnterBackground:(NSNotification *)notification { _shouldStop = NO; // 启动后台任务延长执行窗口(配合定位权限) self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"CompassBackgroundTask" expirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; }]; // 初始化定位管理器并配置后台参数 if (!self.locationManager) { self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; self.locationManager.headingFilter = kCLHeadingFilterNone; self.locationManager.allowsBackgroundLocationUpdates = YES; self.locationManager.pausesLocationUpdatesAutomatically = NO; } // 请求始终授权 [self.locationManager requestAlwaysAuthorization]; [self.locationManager startUpdatingHeading]; } - (void)applicationWillEnterForeground:(NSNotification *)notification { _shouldStop = YES; [self.locationManager stopUpdatingHeading]; if (self.bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; } NSLog(@"Magnetic head position count: %lu", [readings count]); } // 航向更新代理回调,自动获取带时间戳的数据 - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { if (_shouldStop) return; NSDictionary *reading = @{ @"magneticHeading": @(newHeading.magneticHeading), @"timestamp": newHeading.timestamp }; [readings addObject:reading]; } @end - Unity端代码无需修改,保持原调用逻辑即可。
方案二:利用后台任务延长执行时间(短期采集)
若无需长时间采集,仅需延长后台执行时间(最长约180秒),可使用iOS后台任务API:
- 在
applicationDidEnterBackground中启动后台任务:- (void)applicationDidEnterBackground:(NSNotification *)notification { _shouldStop = NO; self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"CompassShortTask" expirationHandler:^{ _shouldStop = YES; [[UIApplication sharedApplication] endBackgroundTask:self.bgTask]; self.bgTask = UIBackgroundTaskInvalid; }]; // 启动指南针采集(同样改用代理回调方式) [self setupCompassWithDelegate]; } - 替换原轮询逻辑为
CLLocationManager代理回调,确保数据准确获取。
相关参考方向
- 掌握
CLLocationManager的官方使用规范,重点理解后台定位配置和代理回调机制 - 学习iOS后台执行模型,区分后台任务、后台权限(定位/音频等)的适用场景
- 熟悉Objective-C内存管理规则,避免局部变量提前释放导致的功能异常
- 了解线程安全的基本处理方式,比如用
@synchronized或串行队列保护共享变量读写
内容的提问来源于stack exchange,提问作者Daniele
相关产品推荐
相关产品推荐

