iPhone - фон для опроса событий - PullRequest
66 голосов
/ 11 января 2011

В течение долгого времени я искал в своем приложении для iPhone способ каждые X минут опрашивать счетчики данных.После большого прочтения документации по фоновому выполнению и нескольких пробных приложений я отклонил это как невозможное, не злоупотребив фоновыми API.

На прошлой неделе я нашел это приложение, которое делает именно это.http://itunes.apple.com/us/app/dataman-real-time-data-usage/id393282873?mt=8

Он работает в фоновом режиме и отслеживает количество использованных вами данных сотовой связи / WiFi.Я подозреваю, что разработчик регистрирует свое приложение в качестве отслеживающего изменения местоположения, но значок служб определения местоположения не виден во время работы приложения, что я считал обязательным требованием.можно сделать?

Ответы [ 7 ]

91 голосов
/ 26 января 2011

Я тоже видел это поведение.После долгих попыток я обнаружил две вещи, которые могли бы помочь.Но я все еще не уверен, как это может повлиять на процесс рецензирования.

Если вы используете одну из фоновых функций, приложение будет запущено iOS в фоновом режиме после его выхода (системой).Мы будем злоупотреблять этим позже.

В моем случае я использовал 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 секунд?), Чтобы закончить задание.Я обнаружил, что это должно быть процессорное время.Так что это означает: если вы ничего не делаете, ваше приложение все еще выполняется!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-Time здесь, и ваше приложение будет работать дольше!Но одно можно сказать наверняка: ваше приложение будет закрыто через некоторое время.Но поскольку вы зарегистрировали свое приложение как VoIP или одно из других, система перезапускает приложение в фоновом режиме, что перезапустит ваш фоновый процесс ;-) С помощью этого PingPong я могу выполнять много фоновых операций.но помните, что нужно уделять больше времени процессору.И сохраните все данные, чтобы восстановить ваши представления - ваше приложение будет закрыто через некоторое время.Чтобы он все еще работал, вы должны вернуться в свое последнее «состояние» после пробуждения.

Я не знаю, является ли это подходом приложений, о которых вы упоминали ранее, но это работает для меня.

Надеюсь, что смогу помочь

Обновление:

После измерения времени задания 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);
        }    
    }); 
}

После истечения времени ожидания вашего приложения будет вызван expirationHandler VoIP, где вы просто перезапустите долгосрочную задачу.Эта задача будет завершена через 600 секунд.Но снова будет вызов обработчика срока действия, который запускает еще одну долгосрочную задачу и т. Д. Теперь вам нужно только проверить, возвращается ли приложение на передний план.Затем закройте bgTask, и все готово.Может быть, можно сделать что-нибудькак это внутри expirationHandler из долгосрочной задачи.Просто попробуйте.Используйте консоль, чтобы увидеть, что происходит ... Развлекайтесь!

Обновление 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 ...

17 голосов
/ 01 декабря 2012

Что работает, а что нет

Не совсем понятно, какой из этих ответов работает, и я потратил много времени, пытаясь их всех.Итак, вот мой опыт работы с каждой стратегией:

  1. Взлом VOIP - работает, но сделает вас отвергнут, если вы не приложение VOIP
  2. Рекурсивно beginBackgroundTask... - не работает.Он выйдет через 10 минут.Даже если вы попробуете исправления в комментариях (по крайней мере, комментарии до 30 ноября 2012 г.).
  3. Silent Audio - работает, но люди были отклонены за это
  4. Локальные / Push-уведомления- требуется взаимодействие с пользователем, прежде чем ваше приложение будет разбужено
  5. Использование фонового расположения - работает .Вот подробности:

Обычно вы используете фоновый режим «расположение», чтобы приложение работало в фоновом режиме.Это работает, даже если пользователь не разрешает обновления местоположения.Даже если пользователь нажимает кнопку «Домой» и запускает другое приложение, оно все равно будет работать.Кроме того, это разрядка аккумулятора, и он может растянуть процесс утверждения, если ваше приложение не имеет никакого отношения к местоположению, но, насколько я знаю, это единственное решение, которое имеет хорошие шансы быть одобренным.

Вот как это работает:

В вашем списке параметров:

  • Приложение не работает в фоновом режиме: НЕТ
  • Необходимые фоновые режимы: местоположение

Затем обратитесь к платформе CoreLocation (в фазах сборки) и добавьте этот код где-нибудь в своем приложении (до того, как он перейдет в фоновый режим):

#import <CoreLocation/CoreLocation.h>

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

Примечание: startMonitoringSignificantLocationChanges будет не работа.

Стоит также упомянуть, что если ваше приложение рухнет, то iOS не вернет его к жизни.Взлом VOIP - единственный, который может вернуть его.

6 голосов
/ 07 февраля 2012

Существует еще один способ остаться в фоновом режиме - запуск / остановка менеджера местоположений в фоновой задаче , сброс таймера фона, когда 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
1 голос
/ 23 февраля 2011

Я попробовал обновление 2, но оно просто не работает.Когда вызывается обработчик истечения, он завершает фоновую задачу.Затем запуск новой фоновой задачи просто вызывает немедленный вызов обработчика истечения срока действия (таймер не сбрасывается и все еще истек).Таким образом, я получил 43 запуска / остановки фоновых задач, прежде чем приложение было приостановлено.

1 голос
/ 24 января 2011

Если это не GPS, я думаю, что единственный другой способ сделать это - функция фоновой музыки, то есть играть 4 "33" все время, пока она включена.И то, и другое похоже на злоупотребление API-интерфейсами фоновой обработки, и поэтому потенциально может зависеть от капризов процесса проверки.

0 голосов
/ 24 апреля 2019

Это довольно старый вопрос, но правильный способ сделать это сейчас - через Фоновое обновление приложения , которое полностью поддерживается ОС и не требует взломов.

Обратите внимание, что операционная система все еще зависит от того, когда на самом деле произойдет обновление вашего фона. Вы можете установить минимальное время опроса, но нет гарантии, что он будет вызываться на этой частоте, поскольку ОС будет применять свою собственную логику для оптимизации времени работы от батареи и использования радиосвязи.

Первое, что вам нужно сделать, это настроить свой проект для поддержки фонового обновления на вкладке «Возможности». Это добавит необходимые ключи к вашему Info.plist:

enter image description here

Затем вам нужно добавить кое-что к вашему AppDelegate, что должно реализовать как URLSessionDelegate, так и 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
}
0 голосов
/ 03 декабря 2011

в моих тестах на iOS5 я обнаружил, что помогает запустить мониторинг CoreLocation, через startMonitoringForLocationChangeEvents (не SignificantLocationChange), точность не имеет значения, и даже на этом iPod, если я это делаю, это так - backgroundTimeRemaining никогда не падает.

...