NSTimer在后台运行一段时间后停止触发。

4

我正在开发一个应用程序,在其中需要每30秒进行API调用,因此我创建了NSTimer来完成它。但是当我的应用进入后台后,计时器停止触发3-4分钟。因此,它只能在后台工作3-4分钟,之后就无法继续工作。我该如何修改我的代码,使计时器不会停止。

以下是我的一些代码。

- (IBAction)didTapStart:(id)sender {
    NSLog(@"hey i m in the timer ..%@",[NSDate date]);
    [objTimer invalidate];
    objTimer=nil;

    UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
    UIApplication  *app = [UIApplication sharedApplication];
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
    }];
    objTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self
                                                       selector:@selector(methodFromTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:objTimer forMode:UITrackingRunLoopMode];
}

-(void)methodFromTimer{
    [LOG debug:@"ViewController.m ::methodFromTimer " Message:[NSString stringWithFormat:@"hey i m from timer ....%@",[NSDate date] ]];
    NSLog(@"hey i m from timer ....%@",[NSDate date]);
}

我甚至用以下代码进行了更改:

我甚至用以下代码进行了更改:

[[NSRunLoop mainRunLoop] addTimer:objTimer forMode:NSRunLoopCommonModes];

这也没有起作用。

2
如果不使用其中一种后台模式,您只能在后台完成任务的时间为3分钟(在iOS 7上为3分钟)。请参阅此处:https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html,以确定适合您情况的后台模式。 - Guy S
4个回答

8
不要将UIBackgroundTaskIdentifier任务创建为局部变量,而应该像下面一样将其创建为全局变量:
步骤-1 Initiating varible 步骤-2 ViewDidLoad Method and added barButton 步骤-3 BarButton method call 步骤-4 Timer method call 当本地变量失去作用域时,全局变量不会失去。我创建了一个演示,并使用1秒重复计时器运行了一段时间,工作很顺利。如果您仍然遇到问题,请告诉我。
我再次运行演示,并记录了它的日志。它正常运行了3分钟以上。虽然逻辑上是3分钟,但由于uibackgroundtask已经初始化,因此它不应该让定时器的这个任务被终止。
编辑部分: bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ [app endBackgroundTask:bgTask]; //删除此行代码后,它将一直运行,直到计时器停止,当应用程序被杀死时,则自动删除所有变量和范围。 }]; 请检查并告诉我是否有效。
嘿,我运行了你的代码,并到达了expirationHandler,但是在释放调试点后,计时器顺利运行。 Highlighted part is when I reached handler

谢谢Nikhil, 我按照您的解决方案尝试了,但它没有起作用。 我的定时器再次在3分钟后停止。 - Vivek Shah
如果仍不满意,则请提供您的源代码,我们将彻底检查。 - nikhil84
如果我删除“[app endBackgroundTask:bgTask]”这一行,那么我的计时器在后台甚至不会调用一次。如果您能处理它,请告诉我如何提供源代码。 - Vivek Shah
这个"[app endBackgroundTask:bgTask]"代码只是为了结束你的后台任务(同时也会结束你的计时器),所以它与你的计时器启动没有任何关系。我会在我的帖子中更新逐步代码。 - nikhil84
通过 Github,你可以上传你的代码并分享链接。 - nikhil84
显示剩余11条评论

2

这个方法对我有用,所以我将它添加到StackOverflow上供未来的回答者参考。

在启动计时器之前,请添加以下实用方法。当我们调用AppDelegate.RestartBackgroundTimer()时,它将确保您的应用程序保持活动状态 - 即使它在后台运行或屏幕被锁定。但是,这只能保证3分钟(正如您所提到的):

class AppDelegate: UIResponder, UIApplicationDelegate {
    static var backgroundTaskIdentifier: UIBackgroundTaskIdentifier? = nil;

    static func RestartBackgroundTimer() {
        if (AppDelegate.backgroundTaskIdentifier != nil) {
            print("RestartBackgroundTimer: Ended existing background task");
            UIApplication.sharedApplication().endBackgroundTask(AppDelegate.backgroundTaskIdentifier!);
            AppDelegate.backgroundTaskIdentifier = nil;
        }
        print("RestartBackgroundTimer: Started new background task");
        AppDelegate.backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
            UIApplication.sharedApplication().endBackgroundTask(AppDelegate.backgroundTaskIdentifier!);
            AppDelegate.backgroundTaskIdentifier = nil;
        })
    }
}

同时,在启动应用程序时,请确保以下内容运行。这将确保即使应用程序在后台运行,音频也会播放(并且在此过程中,请确保您的 Info.plist 包含“必需的后台模式”,并且它的集合中包含“使用 AirPlay 播放音频或流音频/视频”的“音频”):

import AVFoundation;

// setup audio to not stop in background or when silent
do {
    try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback);
    try AVAudioSession.sharedInstance().setActive(true);
} catch { }

现在,在需要计时器运行超过3分钟(如果在后台),当后台时间只剩30秒时,您需要播放声音。这将重置剩余的后台时间为3分钟(只需使用例如AudaCity创建“Silent.mp3”并将其拖放到XCode项目中)。
为了总结所有内容,请执行以下操作:
import AVFoundation

class MyViewController : UIViewController {
    var timer : NSTimer!;

    override func viewDidLoad() {
        // ensure we get background time & start timer
        AppDelegate.RestartBackgroundTimer();
        self.timer = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: #selector(MyViewController.timerInterval), userInfo: nil, repeats: true);
    }

    func timerInterval() {
        var bgTimeRemaining = UIApplication.sharedApplication().backgroundTimeRemaining;
        print("Timer... " + NSDateComponentsFormatter().stringFromTimeInterval(bgTimeRemaining)!);
        if NSInteger(bgTimeRemaining) < 30 {
            // 30 seconds of background time remaining, play silent sound!
            do {
                var audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Silent", ofType: "mp3")!));
                audioPlayer.prepareToPlay();
                audioPlayer.play();
            } catch { }
        }
    }
}

1
因为这几乎肯定会导致您的应用程序被应用商店拒绝,所以被踩了。 - Tamás Zahola

2
不要使用NSTimer进行后台任务,它不能按照你的预期工作。你应该只使用由苹果提供的后台抓取API。你可以在该API中设置你想要调用的持续时间。虽然通常不建议设置所需调用的持续时间。请查看此苹果后台编程文档
此外,为了让你快速入门,你可以参考这个Appcoda教程

1

这是正常的行为。

iOS7之后,您只能获得3分钟的后台时间。在此之前,如果我没记错的话,是10分钟。要延长后台时间,您的应用程序需要使用一些特殊服务,例如位置、音频或蓝牙,这将使其在后台保持“活动状态”。

另外,即使您使用其中一项服务,“后台应用程序刷新”设置也必须在设备上启用应用程序。

有关详细信息,请参见this答案或文档的后台执行部分。


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