setKeepAliveTimeout and BackgroundTasks

8
我对这个主题感到非常头痛。我正在开发一个应用程序,需要定期轮询Web服务器,以检查是否有新数据。根据返回的信息,我希望向用户推送本地通知。
我知道这种方法与苹果所描述的略有不同,其中远程服务器完成工作,基于APNS推送远程通知。然而,有许多原因使我无法考虑采用这种方法。其中一个原因是用户认证机制。出于安全原因,远程服务器无法考虑用户凭据。我能做的就是将登录和获取核心移动到客户端(iPhone)上。
我注意到苹果为应用程序提供了一种机会,即唤醒并保持打开套接字连接(即VoIP应用程序)。
因此,我开始探索这种方式。在plist中添加所需的信息后,我能够通过在我的appDelegate中使用类似以下内容来“唤醒”我的应用程序:
[[UIApplication sharedApplication] setKeepAliveTimeout:1200 handler:^{ 
    NSLog(@"startingKeepAliveTimeout");
    [self contentViewLog:@"startingKeepAliveTimeout"];
    MyPushOperation *op = [[MyPushOperation alloc] initWithNotificationFlag:0 andDataSource:nil];
    [queue addOperation:op];
    [op release];
}];

然后NSOperation使用以下块代码启动后台任务:
#pragma mark SyncRequests
-(void) main {
    NSLog(@"startSyncRequest");
    [self contentViewLog:@"startSyncRequest"];
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ 
        NSLog(@"exipiration handler triggered");
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
        [self cancel];
    }];


        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSMutableURLRequest *anURLRequest;
            NSURLResponse *outResponse;
            NSError *exitError;
            NSString *username;
            NSString *password;

            NSLog(@"FirstLogin");
            anURLRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:webserverLogin, username, password]]];
            [anURLRequest setHTTPMethod:@"GET"];
            [anURLRequest setTimeoutInterval:120.00];
            [anURLRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];

            exitError = nil;
            NSData *tmpData = [NSURLConnection sendSynchronousRequest:anURLRequest returningResponse:&outResponse error:&exitError];
            [anURLRequest setTimeoutInterval:120.00];
            if(exitError != nil) { //somethings goes wrong
                NSLog(@"somethings goes wrong");
                [app endBackgroundTask:bgTask];
                bgTask = UIBackgroundTaskInvalid;
                [self cancel];
                return;
            }

            //do some stuff with NSData and prompt the user with a UILocalNotification

            NSLog(@"AlltasksCompleted");
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
            [self cancel];
        });
    }
}

上述代码似乎有时候是有效的,但很多其他情况下却会导致我的应用程序崩溃,日志信息如下:
Exception Type:  00000020
Exception Codes: 0x8badf00d
Highlighted Thread:  3

Application Specific Information:
DemoBackApp[5977] has active assertions beyond permitted time: 
{(
    <SBProcessAssertion: 0xa9da0b0> identifier: UIKitBackgroundCompletionTask process: DemoBackApp[5977] permittedBackgroundDuration: 600.000000 reason: finishTask owner pid:5977 preventSuspend  preventIdleSleep 
)}

Elapsed total CPU time (seconds): 0.010 (user 0.010, system 0.000), 100% CPU 
Elapsed application CPU time (seconds): 0.000, 0% CPU

对于那些询问的人,是的。我也尝试过异步的NSURLConnection方法。但是没有用。即使我使用带有超时处理程序和didFinishLoading:WithError的异步方法,它仍然会崩溃。

我卡住了。非常感谢任何提示。

4个回答

9

这是一个旧帖子,但应该更新一下。

截至 iOS 6,我在VoIP计时器后台方法方面看到的行为如下:

  • 通过 App Review 过程,VoIP BackgroundMode 仍然严格禁止应用商店应用程序使用
  • 最小 KeepAlive 时间为 600 秒;任何少于该时间的值都会使处理程序安装失败(并向 NSLog 发送警告)
  • 将 keepAlive 时间设置为显着大于 600 秒的值通常会导致处理程序以每time/2间隔频率触发。额外的事实:这与 SIP REGISTER 请求一致,在其中推荐的重新注册间隔为 .5*re-register 时间。
  • 当调用 keepAlive 处理程序时,我观察到以下情况:
    • 您将获得约 10 秒 的“前景”执行时间,在此期间剩余的后台时间是无限的(由 backgroundTimeRemaining 返回)
    • 如果您在 keepAlive 处理程序中启动了 beginBackgroundTask,我观察到您将获得 60 秒 的后台执行时间(由 backgroundTimeRemaining 返回)。这与用户从您的应用程序处于活动状态到背景状态转换时获得的 600 秒 不同。我没有发现任何扩展此时间的方法(除非使用其他技巧,如位置等)

希望这会有所帮助!


7
当您调用-setKeepAliveTimeout:handler:时,您只有最多30秒的时间来完成所有工作并暂停。当您的应用程序首次转换到后台时,您将不会获得相同的背景优先级。这是为了完成长时间运行的任务,关闭一些东西等。
通过VOIP回调,您只需发送所需的ping数据包以保持网络连接处于活动状态且不超时。无论何时,如果您的应用程序仍在执行,并且超过30秒,即使开始新的后台任务,您也将被终止。
此外,重要的是要注意,如果您实际上不是VOIP应用程序或者在VOIP回调窗口期间执行与保持网络连接打开无关的任何操作,则您的应用程序被拒绝在应用商店中上架。当您设置任何保持活动标志(VOIP、背景音乐、导航)时,他们会进行严格的测试,以确保它在后台时仅执行其标记的操作。几乎可以肯定,执行任何类型的HTTP GET请求并等待某些大型数据更新返回几乎肯定会导致您的应用程序被拒绝。
编辑:正如评论中Patrick所指出的那样,块执行的当前时间已从iOS 5的30秒减少到10秒。每当重新链接应用程序以获取新版本的SDK时,始终至少快速检查文档以确保它们已更新(随着iOS 6的推出,这个数字可能会再次调整)。

非常感谢您的回复。首先,拒绝并不是问题,因为该应用程序是一个私有应用程序,而不是针对AppStore的。关于30秒的超时,我已经阅读了相关内容,但我无法理解为什么崩溃日志告诉我超时时间为600.00秒,而不是30.00秒。此外,有时方法有效,有时则无效。 - valvoline
@valvoline:如果它不起作用,那么你的后台任务显然花费了太长时间。文档说30秒,但也许这只是软性数字,操作系统实际上给你10分钟,我不确定。我们知道,在某些时候会有一些阻塞,导致你的事情无法完成,由于VIOP是为快速检查而设计的,因此操作系统的错误处理可能会混淆(例如不调用取消块或其他内容)。 - Jason Coco
@jason:你有关于这个VOIP窗口他们需要什么具体信息吗?它在公开的地方说明了吗? - Aviad Ben Dov
对于那些想了解超时的人,在这里阅读文档:http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#jumpTo_41文档中指出,保持活动状态之间至少需要600秒,并且你只有10秒的时间执行所需的操作,而不是30秒。 - Patrick Horn
@PatrickHorn 感谢您添加文档链接。当编写此答案时,该应用程序被赋予30秒的时间来执行其VoIP套接字上所需的任何维护工作。随着iOS 5的推出,这个时间被缩短到了10秒。使用iOS 4 SDK链接的应用程序仍然有30秒的时间,而新的应用程序只有10秒。 - Jason Coco
在调试应用程序时,你需要记住这个时间限制。我原以为我的代码会崩溃,结果实际上是应用程序被关闭了。 - gheese

6

针对iOS7更新此行为的说明:

  • 当您的应用程序首次进入后台时,您将获得180秒的任务时间,由backgroundTimeRemaining报告。然而,在backgroundTimeRemaining报告的时间耗尽5秒之前,它将停止响应。
  • 当keepAlive任务触发时,backgroundTimeRemaining报告的是10秒的前台时间,随后是60秒的后台计时器。同样,在backgroundTimeRemaining报告的时间耗尽5秒之前,它也会停止响应。

因此,在iOS7上,您可以每10分钟获得65秒的处理时间。


3
似乎可以将保持连接超时的处理器与请求有限后台任务执行相结合,从而在被调用时每次都可以获得完整的10分钟时间(而不是通常的10-30秒钟)。但仍然存在上面提到的问题 --- 您需要在plist中设置VOIP标志才能提交,如果您没有实际上是VOIP应用程序并且拥有该标志,那么苹果很可能不会接受您的应用程序。但对于内部分发(企业或其他),此解决方案应该可以为您提供后台运行时间。
在我的测试中,每次调用VOIP处理程序时(无论用户是否将应用程序移至前台),10分钟的有限执行计时器都会被重置,这意味着您可以每隔600秒(10分钟)在后台轮询服务器,而轮询过程可以在进入休眠之前长达10分钟(如果您需要几乎不间断的后台操作,则可以实现此目的)。
再次强调,除非您能说服他们您是VOIP,否则App Store并不是一个真正的选择。

你在´setKeepAliveTimeout´处理程序中手动重置执行计时器吗?能否提供一些示例代码?我处于相同的情况,正在使用VOIP(不打算提交到AppStore)。当应用程序被´setKeepAliveTimeout´唤醒时,我的执行计时器没有自动重置。但似乎我可以通过以与´applicationDidEnterBackground´相同的方式创建一个新的后台任务来重置它。 - reekris
是的,每次调用setKeepAliveTimeout时,我都会创建一个新的有限后台任务(似乎可以获得新的10分钟运行时间)。 - BadPirate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接