Skip to content

Background mode (iOS)

Łukasz Rejman edited this page Jun 15, 2020 · 3 revisions

This document describes how background mode is implemented on iOS concerning the BLE functionality and how the React Native application should cooperate. I'll start by listing the most important external resources, which I highly recommend to go through during this read:

  1. React Native iOS project used to test all of the functionalities presented here.
  2. nRF52 firmware project, which implements the BLE device used during testing and by the app mentioned above.
  3. Documentation about BLE background mode.
  4. Documentation about Background Task.
  5. Developer's hints about Background Task implementation.
  6. Documentation about App Refresh & Processing background tasks.

Important note:

  • We don't guarantee that knowledge described here is up to date.
  • All assumptions may be invalid and are coming from our testing only.
  • We won't suggest you React Native packages implementing background mode functionality. Everything is written from scratch.

React Native code structure

When working in background mode it is important to cleanly separate UI code from the BLE/data processing code. Invoke code only from dependencies, which doesn't assume that UI is available. Otherwise, you might run into problems:

  • Sending changes to the UI may block application.
  • React Native timers might not be properly functioning in background mode.

The suggested approach would be to create BleManager instance globally in one file and define all processing functions without UI dependency. If there are any listeners/callbacks, which could be registered by the UI elements, be sure to disable them in background mode.

Preparing for background mode

By default, the iOS application operates in the foreground mode, which means that as soon as the application moves to the background all Bluetooth operations are suspended. You will be notified about any BLE events only when the app goes back to the foreground.

To fix that developer needs to declare that it supports a bluetooth-central background execution mode in its Info.plist file (check README file for instructions). Apart from that one need to watch out for a few additional restrictions:

  • Scan properly allowDuplicates is ignored in background mode.
  • You have to specify service UUID as the first argument of bleManager.startDeviceScan function to be able to discover the device in background mode. That means your peripheral should advertise specified service UUID in advertisement data. That's important.

When the application is suspended in the background, iOS may kill it. In that case, all BLE communication will be lost. To prevent that, the developer can opt-in into the state restoration mechanism. It will keep all scanning procedures, current connections alive and relaunch the app if any BLE event occurs.

It's easy to support restoration state. Just add restoreStateIdentifier and restoreStateFunction when BleManager is constructed:

const manager = new BleManager({
  restoreStateIdentifier: 'BleInTheBackground',
  restoreStateFunction: restoredState => {
    if (restoredState == null) {
      // BleManager was constructed for the first time.
    } else {
      // BleManager was restored. Check `restoredState.connectedPeripherals` property.
    }
  },
});

Background mode operation

After the application goes to the background, there is a short time when it's operational, but after a few seconds the app goes to suspended mode. When restoreStateIdentifier is specified, iOS takes care of any pending connection, keeping it alive and queueing all BLE events. I hope that the diagram below explains it:

                        +------------+
                        | Foreground |
                        +---+----^---+
                            |    |
           User changed app |    | User went back
                            |    |
                        +---v----+---+
                  +---->+ Background <------+
   Scheduled task |     +-------+----+      |
   or BLE event   |             |           |
                  |             |           |
                  |             |           |
            +-----+-----+       |           |
            | Suspended <-------+           | Scheduled task
            +-----+-----+  Run out of       | or BLE event
                  |        computing time   |
                  |                         |
Lack of resources |                         |
                  |                         |
             +----v---+                     |
             | Killed +---------------------+
             +--------+

Extending background time

There is an option to extend background time using Background Task API. The developer specifies a moment when the long-running task is started and tells when it's finished using Task ID as an identifier. API name is a bit misleading here and it's important to note that we don't create any new process here, but only marking start and end of operations.

Notes:

  • Background task can last for ~15 min. After that time it's expired and the app gets suspended.
  • You can create multiple background tasks sequentially and still get 15 min of processing time.
  • When the app wakes up from suspended mode and background task is created, the application has around 35 seconds to do their job before going to suspended mode again.
  • Firmware can emit BLE events more frequently than 35 sec keeping application from suspending. This, however, is not something that the app should do.

Checkout example source code to see how BackgroundTask API is used in practice.

Simulating killed state

As the above diagram shows, there is a possibility that the application may get killed after a certain amount of time. It's worth to be able to simulate that behavior to test if restoredStateFunction is properly implemented.

To do that you can use Xcode debugger:

  1. Connect your phone and put the application to the background.
  2. From the Xcode menu select: Debug / Attach to Process / YourProcessName.
  3. Stop debugger by clicking a button or from the menu: Debug / Pause.
  4. In LLDB console type: p (int)raise(9); which kills the process.
  5. Resume debugger by clicking a button or from the menu: Debug / Continue.
  6. Reopen application.

If you were connected to the device and opted-in into restoration state after killing application iOS should keep it alive and restoredStateFunction should be called with list of connected devices.

Scheduling work after ~15 min

In normal circumstances, BackgroundTask can give you around 15 minutes of processing time. If you need more or you want to schedule a job after a certain period, you should use the BGProcessingTask under the hood.

Setup

To enable background processing:

  • Click on your project in the Xcode, go to Settings & Capabilities and enable Background processing in the Background Modes group.
  • Open Info.plist file and add your task identifier to the array under the BGTaskSchedulerPermittedIdentifiers key.
  • Register your task before application finishes launch.
  • Look into implementation in the example.

Execution

After specified amount of time, which should be higher than 15 minutes after application went to the background mode, app should wake up and start the processing task.

To start background task from Xcode debugger type:

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"TASK_IDENTIFIER"]

To abort background task from Xcode debugger type:

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"TASK_IDENTIFIER"]