iPhone - 后台投票事件

发布于 2024-10-11 06:15:05 字数 439 浏览 4 评论 0原文

很长一段时间以来,我一直在研究一种在我的 iPhone 应用程序中每隔 X 分钟轮询一次以检查数据计数器的方法。在阅读了大量后台执行文档和一些试用应用程序后,我认为在不滥用后台 API 的情况下这是不可能的。

上周我发现这个应用程序正是这样做的。 http://itunes.apple.com /us/app/dataman-real-time-data-usage/id393282873?mt=8

它在后台运行并跟踪您使用的蜂窝/WiFi 数据计数。我怀疑开发人员正在将其应用程序注册为跟踪位置更改,但在应用程序运行时位置服务图标不可见,我认为这是一个要求。

有谁知道如何实现这一点的任何线索?

For quite a while I'd been looking into a way in my iPhone app to poll every X minutes to check the data counters. After much reading of the Background Execution documentation and a few trial apps I'd dismissed this as impossible without abusing the background APIs.

Last week I found this application which does exactly that. http://itunes.apple.com/us/app/dataman-real-time-data-usage/id393282873?mt=8

It runs in the background and keeps track of the count of Cellular/WiFi data you've used. I suspect that the developer is registering his app as tracking location changes but the location services icon isn't visible while the app is running, which I thought was a requirement.

Does anyone have any clues as to how this can be accomplished?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

你在看孤独的风景 2024-10-18 06:15:05

我也见过这种行为。经过多次尝试后,我发现了两件事,这可能会有所帮助。但我仍然不确定这会如何影响审查过程。

如果您使用其中一种后台功能,则应用程序在退出(由系统)后将由 iOS 在后台再次启动。我们稍后会滥用这一点。

就我而言,我使用了在我的 plist 中启用的 VoIP 后台。
这里的所有代码都是在您的 AppDelegate: 中完成的

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");
    // try to do sth. According to Apple we have ONLY 30 seconds to perform this Task!
    // Else the Application will be terminated!
    UIApplication* app = [UIApplication sharedApplication];
    NSArray*    oldNotifications = [app scheduledLocalNotifications];

     // Clear out the old notification before scheduling a new one.
    if ([oldNotifications count] > 0) [app cancelAllLocalNotifications];

    // Create a new notification
    UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
    if (alarm)
    {
        alarm.fireDate = [NSDate date];
        alarm.timeZone = [NSTimeZone defaultTimeZone];
        alarm.repeatInterval = 0;
        alarm.soundName = @"alarmsound.caf";
        alarm.alertBody = @"Don't Panic! This is just a Push-Notification Test.";

        [app scheduleLocalNotification:alarm];
    }
}

,注册是在

- (void)applicationDidEnterBackground:(UIApplication *)application {

    // This is where you can do your X Minutes, if >= 10Minutes is okay.
    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }
}

现在神奇的事情发生了:我什至不使用 VoIP 套接字。但是这个 10 分钟回调提供了一个很好的副作用:10 分钟后(有时更早)我发现我的计时器和之前的跑步步骤正在执行一小段时间。如果您将一些 NSLog(..) 放入代码中,您可以看到这一点。这意味着,这个短暂的“唤醒”会执行代码一段时间。根据 Apple 的说法,我们还剩 30 秒的执行时间。我假设,像线程这样的后台代码正在执行近 30 秒。如果您必须“有时”检查某些内容,那么这是有用的代码。

该文档表示,如果应用程序终止,所有后台任务(VoIP、音频、位置更新)将在后台自动重新启动。 VoIP 应用程序将在启动后自动在后台启动!

通过滥用这种行为,您可以使您的应用程序看起来像是“永远”运行。
注册一个后台进程(即 VoIP)。这将导致您的应用程序在终止后重新启动。

现在编写一些“任务必须完成”代码。根据 Apple 的说法,您还有一些时间(5 秒?)来完成任务。我发现,这一定是CPU时间。这意味着:如果您什么都不做,您的应用程序仍在执行!如果您完成了工作,Apple 建议调用过期处理程序。在下面的代码中,您可以看到,我在expirationHandler 处有一条注释。只要系统允许您的应用程序运行,这就会导致您的应用程序运行。所有计时器和线程都会保持运行,直到 iOS 终止您的应用程序。

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // you can do sth. here, or simply do nothing!
    // All your background treads and timers are still being executed
    while (background) 
       [self doSomething];
       // This is where you can do your "X minutes" in seconds (here 10)
       sleep(10);
    }

    // And never call the expirationHandler, so your App runs
    // until the system terminates our process
    //[app endBackgroundTask:bgTask];
    //bgTask = UIBackgroundTaskInvalid;

    }); 
}

在这里节省 CPU 时间,您的应用程序运行时间会更长!但有一件事是肯定的:您的应用程序将在一段时间后被终止。但由于您将应用程序注册为 VoIP 或其他应用程序之一,系统会在后台重新启动该应用程序,这将重新启动您的后台进程;-)
有了这个 PingPong,我可以做很多背景工作。但请记住要非常节省 CPU 时间。并保存所有数据,以恢复您的视图 - 您的应用程序将在一段时间后终止。为了使其看起来仍在运行,您必须在唤醒后跳回到最后的“状态”。

我不知道这是否是您之前提到的应用程序的方法,但它对我有用。

希望我能帮忙

更新:

测量BG任务的时间后,有一个惊喜。 BG 任务限制为 600 秒。这是 VoIP 最短时间 (setKeepAliveTimeout:600) 的确切最短时间。

因此,此代码会导致后台“无限”执行:

标头:

UIBackgroundTaskIdentifier bgTask; 

代码:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    while (1) {
        NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
        sleep(1);
    }   
});     

- (void)applicationDidEnterBackground:(UIApplication *)application {

    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        while (1) {
            NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
           sleep(1);
        }    
    }); 
}

应用程序超时后,将调用 VoIP过期处理程序,您只需重新启动长时间运行的任务即可。该任务将在 600 秒后终止。但是将会再次调用过期处理程序,这会启动另一个长时间运行的任务,等等。现在您只需检查应用程序是否返回前台。然后关闭bgTask,就完成了。也许一个人可以做某事。像这样在长时间运行的任务的过期处理程序中。试试吧。使用您的控制台,看看会发生什么...玩得开心!

更新 2:

有时简化事情会有所帮助。我的新方法是这样的:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    // it's better to move "dispatch_block_t expirationHandler"
    // into your headerfile and initialize the code somewhere else
    // i.e. 
    // - (void)applicationDidFinishLaunching:(UIApplication *)application {
//
// expirationHandler = ^{ ... } }
    // because your app may crash if you initialize expirationHandler twice.
    dispatch_block_t expirationHandler;
    expirationHandler = ^{

        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;


        bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
    };

    bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // inform others to stop tasks, if you like
        [[NSNotificationCenter defaultCenter] postNotificationName:@"MyApplicationEntersBackground" object:self];

        // do your background work here     
    }); 
}

无需 VoIP 黑客攻击即可工作。根据文档,如果执行时间结束,将执行过期处理程序(在本例中是我的“expirationHandler”块)。通过将块定义为块变量,可以在过期处理程序中再次递归地启动长时间运行的任务。这也会导致无休止的执行。

如果您的应用程序再次进入前台,请注意终止任务。如果不再需要该任务,则终止该任务。

根据我自己的经验,我测量了一些东西。
在打开 GPS 无线电的情况下使用位置回调会很快耗尽我的电池电量。使用我在更新 2 中发布的方法几乎不消耗任何能量。根据“用户体验”,这是一个更好的方法。也许其他应用程序是这样工作的,将其行为隐藏在 GPS 功能后面......

I have seen this behavior, too. After trying a lot I discovered two things, which could help. But I am still uncertain how this may influence the reviewing process.

If you use one of the backgrounding features, the app will be launched by iOS in background again once it was quit (by the system). This we will abuse later.

In my case I used VoIP backgrounding enabled in my plist.
All the code here is done in your AppDelegate:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");
    // try to do sth. According to Apple we have ONLY 30 seconds to perform this Task!
    // Else the Application will be terminated!
    UIApplication* app = [UIApplication sharedApplication];
    NSArray*    oldNotifications = [app scheduledLocalNotifications];

     // Clear out the old notification before scheduling a new one.
    if ([oldNotifications count] > 0) [app cancelAllLocalNotifications];

    // Create a new notification
    UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
    if (alarm)
    {
        alarm.fireDate = [NSDate date];
        alarm.timeZone = [NSTimeZone defaultTimeZone];
        alarm.repeatInterval = 0;
        alarm.soundName = @"alarmsound.caf";
        alarm.alertBody = @"Don't Panic! This is just a Push-Notification Test.";

        [app scheduleLocalNotification:alarm];
    }
}

and the registration is done in

- (void)applicationDidEnterBackground:(UIApplication *)application {

    // This is where you can do your X Minutes, if >= 10Minutes is okay.
    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }
}

Now the magic happens: I don't even use VoIP-Sockets. But this 10 Minutes callback provides a nice side effect: After 10 Minutes (sometimes earlier) I discovered that my timers and previous running treads are being executed for a short while. You can see this, if you place some NSLog(..) into your code. This means, that this short "wakeup" executes the code for a while. According to Apple we have 30 seconds execution time left. I assume, that background code like threads are being executed for nearly 30 seconds. This is useful code, if you must "sometimes" check something.

The doc says that all background tasks (VoIP, audio, location updates) will be automatically restarted in background if the app was terminated. VoIP apps will be started in background automatically after bootup!

With abusing this behavior, you can make your app be looking like running "forever".
Register for one background process (i.e. VoIP). This will cause your app to be restarted after termination.

Now write some "Task has to be finished" code. According to Apple you have some time (5 seconds?) left to finish tasks. I discovered, that this must be CPU time. So that means: if you do nothing, your app is still being executed! Apple suggest to call an expirationhandler, if you are finished with your work. In the code below you can see, that i have a comment at the expirationHandler. This will cause your app running as long as the system allows your app to be running. All timers and threads stay running until iOS terminates your app.

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // you can do sth. here, or simply do nothing!
    // All your background treads and timers are still being executed
    while (background) 
       [self doSomething];
       // This is where you can do your "X minutes" in seconds (here 10)
       sleep(10);
    }

    // And never call the expirationHandler, so your App runs
    // until the system terminates our process
    //[app endBackgroundTask:bgTask];
    //bgTask = UIBackgroundTaskInvalid;

    }); 
}

Be very spare with CPU-Time here, and your app runs longer! But one thing is for sure: your app will be terminated after a while. But because you registered your app as VoIP or one of the others, the system restarts the app in background, which will restart your background process ;-)
With this PingPong I can do a lot of backgrounding. but remember be very spare with CPU time. And save all data, to restore your views - your app will be terminated some time later. To make it appear still running, you must jump back into your last "state" after wakeup.

I don't know if this is the approach of the apps you mentioned before, but it works for me.

Hope I could help

Update:

After measuring the time of the BG task, there was a surprise. The BG Task is limited to 600 seconds. This is the exact minimum time of the VoIP minimumtime (setKeepAliveTimeout:600).

So THIS code leads into "infinite" execution in background:

Header:

UIBackgroundTaskIdentifier bgTask; 

Code:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    while (1) {
        NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
        sleep(1);
    }   
});     

- (void)applicationDidEnterBackground:(UIApplication *)application {

    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        while (1) {
            NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
           sleep(1);
        }    
    }); 
}

After your app has timed out, the VoIP expirationHandler will be called, where you simply restart a long running task. This task will be terminated after 600 seconds. But there will be again a call to the expiration handler, which starts another long running task, etc. Now you only have to check weather the App is getting back to foreground. Then close the bgTask, and you're done. Maybe one can do sth. like this inside the expirationHandler from the long running task. Just try it out. Use your Console, to see what happens... Have Fun!

Update 2:

Sometimes simplifying things helps. My new approach is this one:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    // it's better to move "dispatch_block_t expirationHandler"
    // into your headerfile and initialize the code somewhere else
    // i.e. 
    // - (void)applicationDidFinishLaunching:(UIApplication *)application {
//
// expirationHandler = ^{ ... } }
    // because your app may crash if you initialize expirationHandler twice.
    dispatch_block_t expirationHandler;
    expirationHandler = ^{

        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;


        bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
    };

    bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // inform others to stop tasks, if you like
        [[NSNotificationCenter defaultCenter] postNotificationName:@"MyApplicationEntersBackground" object:self];

        // do your background work here     
    }); 
}

This is working without the VoIP hack. According to the documentation, the expiration handler (in this case my 'expirationHandler' block) will be executed if execution time is over. By defining the block into a block variable, one can recursively start the long running task again within the expiration handler. This leads into endless execution, too.

Be aware to terminate the task, if your application enters foreground again. And terminate the task if you don't need it anymore.

For my own experience I measured something.
Using the location callbacks with having the GPS radio on is sucking my battery down very quickly. Using the approach which I posted in Update 2 is taking nearly no energy. According to the "userexperience" this is a better approach. Maybe other Apps work like this, hiding its behavior behind GPS functionality ...

零時差 2024-10-18 06:15:05

什么有效?不

完全清楚这些答案中哪些有效?我浪费了很多时间尝试所有这些。以下是我对每种策略的经验:

  1. VOIP hack - 有效,但如果您不是 VOIP 应用程序,将会让您被拒绝
  2. 递归 beginBackgroundTask... - 不起作用。 10 分钟后它将退出。即使您尝试了评论中的修复(至少截至 2012 年 11 月 30 日的评论)。
  3. 无声音频 - 有效,但人们已被拒绝此
  4. 本地/推送通知 - 需要用户交互才能唤醒您的应用
  5. 使用后台位置 - 有效。详细信息如下:

基本上,您使用“位置”后台模式来保持应用程序在后台运行。即使用户不允许位置更新,它也确实有效。即使用户按下主页按钮并启动另一个应用程序,您的应用程序仍然会运行。它也是一个电池消耗者和电池消耗者。如果您的应用程序与位置无关,则审批过程可能会很漫长,但据我所知,这是唯一有可能获得批准的解决方案。

它的工作原理如下:

在您的 plist 集中:

  • 应用程序不在后台运行:否
  • 所需的后台模式:位置

然后引用 CoreLocation 框架(在构建阶段)并将此代码添加到应用程序中的某个位置(之前)它进入后台):

#import <CoreLocation/CoreLocation.h>

CLLocationManager* locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];

注意:startMonitoringSignificantLocationChanges不起作用

还值得一提的是,如果你的应用程序崩溃了,那么 iOS 不会让它恢复正常。 VOIP 破解是唯一可以恢复这种状态的方法。

What Works & What Doesn't

It's not entirely clear which of these answers work & I've wasted a lot of time trying them all. So here's my experience with each strategy:

  1. VOIP hack - works, but will get you rejected if you're not a VOIP app
  2. Recursive beginBackgroundTask... - does not work. It will quit after 10 minutes. Even if you try the fixes in the comments (at least the comments up to Nov 30, 2012).
  3. Silent Audio - works, but people have been rejected for this
  4. Local/Push Notifications - require user interaction before your app will be woken up
  5. Using Background Location - works. Here are the details:

Basically you use the "location" background mode to keep your app running in the background. It does work, even if the user does not allow location updates. Even if the user presses the home button and launches another app, your app will still be running. It's also a battery drainer & may be a stretch in the approval process if your app has nothing to do with location, but as far as I know it's the only solution that has a good chance of being approved.

Here's how it works:

In your plist set:

  • Application does not run in background: NO
  • Required background modes: location

Then reference the CoreLocation framework (in Build Phases) and add this code somewhere in your app (before it goes into the background):

#import <CoreLocation/CoreLocation.h>

CLLocationManager* locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];

Note: startMonitoringSignificantLocationChanges will not work.

It's also worth mentioning that if your app crashes, then iOS will not bring it back to life. The VOIP hack is the only one that can bring it back.

如歌彻婉言 2024-10-18 06:15:05

还有另一种技术可以永远留在后台 - 在后台任务中启动/停止位置管理器,将在 didUpdateToLocation:重置后台计时器被称为。

我不知道它为什么起作用,但我认为 didUpdateToLocation 也被称为任务,从而重置计时器。

根据测试,我相信这就是 DataMan Pro 正在使用的。

请参阅这篇文章https://stackoverflow.com/a/6465280,我从中得到了窍门。

以下是我们应用程序的一些结果:

2012-02-06 15:21:01.520 **[1166:4027] BGTime left: 598.614497 
2012-02-06 15:21:02.897 **[1166:4027] BGTime left: 597.237567 
2012-02-06 15:21:04.106 **[1166:4027] BGTime left: 596.028215 
2012-02-06 15:21:05.306 **[1166:4027] BGTime left: 594.828474 
2012-02-06 15:21:06.515 **[1166:4027] BGTime left: 593.619191
2012-02-06 15:21:07.739 **[1166:4027] BGTime left: 592.395392 
2012-02-06 15:21:08.941 **[1166:4027] BGTime left: 591.193865 
2012-02-06 15:21:10.134 **[1166:4027] BGTime left: 590.001071
2012-02-06 15:21:11.339 **[1166:4027] BGTime left: 588.795573
2012-02-06 15:21:11.351 **[1166:707] startUpdatingLocation
2012-02-06 15:21:11.543 **[1166:707] didUpdateToLocation
2012-02-06 15:21:11.623 **[1166:707] stopUpdatingLocation
2012-02-06 15:21:13.050 **[1166:4027] BGTime left: 599.701993
2012-02-06 15:21:14.286 **[1166:4027] BGTime left: 598.465553

There is another technique to stay forever in the background - starting/stopping location manager in your background task, will reset the background timer when didUpdateToLocation: is called.

I don't know why it works, but I think didUpdateToLocation is also called as a task and thereby resets the timer.

Based on testing, I believe this is what DataMan Pro is using.

See this post https://stackoverflow.com/a/6465280 where I got trick from.

Here are some results from our app:

2012-02-06 15:21:01.520 **[1166:4027] BGTime left: 598.614497 
2012-02-06 15:21:02.897 **[1166:4027] BGTime left: 597.237567 
2012-02-06 15:21:04.106 **[1166:4027] BGTime left: 596.028215 
2012-02-06 15:21:05.306 **[1166:4027] BGTime left: 594.828474 
2012-02-06 15:21:06.515 **[1166:4027] BGTime left: 593.619191
2012-02-06 15:21:07.739 **[1166:4027] BGTime left: 592.395392 
2012-02-06 15:21:08.941 **[1166:4027] BGTime left: 591.193865 
2012-02-06 15:21:10.134 **[1166:4027] BGTime left: 590.001071
2012-02-06 15:21:11.339 **[1166:4027] BGTime left: 588.795573
2012-02-06 15:21:11.351 **[1166:707] startUpdatingLocation
2012-02-06 15:21:11.543 **[1166:707] didUpdateToLocation
2012-02-06 15:21:11.623 **[1166:707] stopUpdatingLocation
2012-02-06 15:21:13.050 **[1166:4027] BGTime left: 599.701993
2012-02-06 15:21:14.286 **[1166:4027] BGTime left: 598.465553
美煞众生 2024-10-18 06:15:05

如果不是GPS,我认为唯一的其他方法就是背景音乐功能,即在启用时一直播放4"33"。两者听起来都有点滥用后台处理 API,因此可能会受到审查过程的突发奇想的影响。

If it's not GPS I think the only other way of doing it is the background music function, i.e., playing 4"33" all the time it's enabled. Both sound like a bit of an abuse of the background processing APIs and so potentially subject to the whims of the review process.

硪扪都還晓 2024-10-18 06:15:05

我尝试了更新 2,但它不起作用。当调用过期处理程序时,它会结束后台任务。然后启动一个新的后台任务只会强制再次立即调用过期处理程序(计时器不会重置并且仍然过期)。因此,在应用程序暂停之前,我启动/停止了 43 次后台任务。

I tried the Update 2 but it just doesn't work. When the expiration handler is called, it ends the background task. Then starting a new background task just forces an immediate call to the expiration handler again (the timer is not reset and is still expired). Thus I got 43 starts/stops of background tasks before the app was suspended.

只是在用心讲痛 2024-10-18 06:15:05

在我对 iOS5 的测试中,我发现通过 startMonitoringForLocationChangeEvents(不是 SignificantLocationChange)启动 CoreLocation 的监控很有帮助,
准确性并不重要
在 iPod 上也是如此
如果我这样做 - backgroundTimeRemaining 永远不会下降。

in my tests on iOS5 i found it helps to have CoreLocation's monitoring started, via startMonitoringForLocationChangeEvents(not SignificantLocationChange),
accuracy does not matter
and it even this way on iPod
if I do that - backgroundTimeRemaining is never goes down.

女中豪杰 2024-10-18 06:15:05

这是一个相当老的问题,但现在正确的方法是通过 后台应用刷新,操作系统完全支持,无需破解。

请注意,关于后台刷新何时实际发生,您仍然受操作系统的支配。您可以设置最短轮询时间,但不能保证以该频率调用它,因为操作系统将应用自己的逻辑来优化电池寿命和无线电使用。

您需要做的第一件事是配置您的项目以支持“功能”选项卡中的后台刷新。这会将必要的键添加到您的 Info.plist 中:

在此处输入图像描述

然后您需要向您的 AppDelegate 添加一些内容,它需要同时实现URLSessionDelegateURLSessionDownloadDelegate

private var _backgroundCompletionHandler: ((UIBackgroundFetchResult) -> Void)?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...

    application.setMinimumBackgroundFetchInterval(5 * 60) // set preferred minimum background poll time (no guarantee of this interval)

    ...
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    _backgroundCompletionHandler = completionHandler

    let sessionConfig = URLSessionConfiguration.background(withIdentifier: "com.yourapp.backgroundfetch")
    sessionConfig.sessionSendsLaunchEvents = true
    sessionConfig.isDiscretionary = true

    let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
    let task = session.downloadTask(with: url)
    task.resume()
}

func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
    NSLog("URLSession error: \(error.localizedDescription)")
    _backgroundCompletionHandler?(.failed)
    _backgroundCompletionHandler = nil
}

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    NSLog("didFinishDownloading \(downloadTask.countOfBytesReceived) bytes to \(location)")
    var result = UIBackgroundFetchResult.noData

    do {
        let data = try Data(contentsOf: location)
        let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)

        // process the fetched data and determine if there are new changes
        if changes {
            result = .newData

            // handle the changes here
        }
    } catch {
        result = .failed
        print("\(error.localizedDescription)")
    }

    _backgroundCompletionHandler?(result)
    _backgroundCompletionHandler = nil
}

This is a rather old question, but the correct way to do this now is via Background App Refresh, which is fully supported by the OS and no hacks are needed.

Note that you are still at the mercy of the OS as to when your background refresh will actually occur. You can set a minimum poll time, but there is no guarantee it will be called at that frequency as the OS will apply its own logic to optimize battery life and radio use.

The first thing you need to do is configure your project to support background refresh in the Capabilities tab. This will add the necessary keys to your Info.plist:

enter image description here

Then you need to add some stuff to your AppDelegate, which needs to implement both URLSessionDelegate and URLSessionDownloadDelegate:

private var _backgroundCompletionHandler: ((UIBackgroundFetchResult) -> Void)?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...

    application.setMinimumBackgroundFetchInterval(5 * 60) // set preferred minimum background poll time (no guarantee of this interval)

    ...
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    _backgroundCompletionHandler = completionHandler

    let sessionConfig = URLSessionConfiguration.background(withIdentifier: "com.yourapp.backgroundfetch")
    sessionConfig.sessionSendsLaunchEvents = true
    sessionConfig.isDiscretionary = true

    let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
    let task = session.downloadTask(with: url)
    task.resume()
}

func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
    NSLog("URLSession error: \(error.localizedDescription)")
    _backgroundCompletionHandler?(.failed)
    _backgroundCompletionHandler = nil
}

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    NSLog("didFinishDownloading \(downloadTask.countOfBytesReceived) bytes to \(location)")
    var result = UIBackgroundFetchResult.noData

    do {
        let data = try Data(contentsOf: location)
        let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)

        // process the fetched data and determine if there are new changes
        if changes {
            result = .newData

            // handle the changes here
        }
    } catch {
        result = .failed
        print("\(error.localizedDescription)")
    }

    _backgroundCompletionHandler?(result)
    _backgroundCompletionHandler = nil
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文