MobBackground (mob_background v0.1.2)

Copy Markdown View Source

Background execution keep-alive — a Mob plugin.

Keeps the BEAM node running when the screen locks or the app is backgrounded. On iOS this is a silent AVAudioEngine session; on Android it is a foreground service. Opt-in: add the dependency and activate it in mob.exs.

Which plugin do I actually want?

The background/wake/notify/push quartet gets mixed up regularly. Four distinct concerns:

I want to…Plugin
Keep my app alive continuously while backgroundedmob_background (this plugin)
Run a handler only when the OS fires a scheduler / silent pushmob_wake (device-side)
Schedule local notifications / register for pushmob_notify (device-side)
Send a push from my servermob_push (server-side; no device code)
  • mob_background vs mob_wake: background is a keep-alive — the app stays running while backgrounded so your own code (an active upload, a walking tracker, a music player) can keep going. Wake is event-driven — the OS (or a silent push) fires a specific handler, it runs to completion, the platform reports done. They stack: an app can use both. Cost/benefit: mob_background needs an ongoing platform justification (audio on iOS, persistent notification on Android); mob_wake doesn't — but doesn't run continuously either.
  • When in doubt: if you're doing episodic work triggered by an external event, mob_wake. If you're doing continuous work the user has explicitly kicked off, mob_background. If you're supporting both patterns in the same app, activate both.

Installation

# mix.exs
{:mob_background, "~> 0.1"}

# mob.exs
config :mob, :plugins, [:mob_background]
config :mob, :trusted_plugins, %{mob_background: "ed25519:<fingerprint>"}

mix mob.plugin.trust mob_background records the fingerprint, then mix mob.deploy --native.

This plugin has two host_requirements the native build warns about on every mix mob.deploy --native of the host — an Android <service> declaration and the iOS UIBackgroundModes plist key. See the Requirements sections below; without them keep_alive/0 starts nothing.

Usage

# Keep the app alive when the screen locks (e.g. in mount/2):
MobBackground.keep_alive()

# Allow suspension again when background execution is no longer needed:
MobBackground.stop()

keep_alive/0 is idempotent — safe to call multiple times.

iOS — silent audio session

iOS suspends apps when the screen locks unless they hold an active background execution mode. keep_alive/0 starts a silent AVAudioEngine looping a zero-filled buffer with AVAudioSessionCategoryOptionMixWithOthers — the OS sees an active audio session and keeps the process running, the user hears nothing, and any music already playing is undisturbed.

Requirements

The app's Info.plist must declare the audio background mode:

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>

This is included in all projects generated by mix mob.new. For Xcode projects, add it under Signing & Capabilities → Background Modes → Audio, AirPlay, and Picture in Picture.

Coexistence with Mob.Audio

Playback (Mob.Audio.play/3): both sides use MixWithOthers, so they mix transparently. The silent buffer is inaudible alongside real audio.

Recording (Mob.Audio.start_recording/2): recording switches the global AVAudioSession category to PlayAndRecord, which sends an interruption to the keep-alive engine. The engine stops — but the recording itself holds an active audio session, so the app stays alive for the duration of the recording. When stop_recording/1 is called and the session is released, iOS fires AVAudioSessionInterruptionTypeEnded and the keep-alive engine restarts automatically. No Elixir code is needed to handle this transition.

The same automatic restart applies to phone calls and any other event that temporarily takes the audio session away from the app.

Known limitation — observer cleanup

Internally, the NIF registers an NSNotificationCenter observer for AVAudioSessionInterruptionNotification to handle the recording restart described above. When stop/0 is called, it removes that observer by the token it stored, so it only removes its own observer.

Apple's stance

Apple permits the audio background mode for apps that legitimately use audio. Mob apps that use Mob.Audio recording or playback qualify. Apple will reject apps that declare this mode without any audio feature — do not add UIBackgroundModes: [audio] to an app that has no audio functionality.

Android — foreground service

On Android the OS equivalent of iOS background execution is a foreground service. keep_alive/0 starts BeamForegroundService, which starts in the foreground with type dataSync and a low-priority persistent notification. The OS will not kill a foreground service under memory pressure and will not pause the process when the screen locks.

Visible notification (required by Android)

Android requires every foreground service to have a notification. It uses the app name and the text "Running in background", with IMPORTANCE_LOW so it makes no sound or vibration. There is no API to hide it.

On Android 13+ (API 33) the notification is only shown in the status bar and drawer when the app holds POST_NOTIFICATIONS — declared and granted at runtime (activate mob_notify and request :notifications). Without it the foreground service still runs (verified on Android 15) but users only see it in the system's running-apps list.

Requirements

The FOREGROUND_SERVICE permissions are added automatically when the plugin is activated. The host AndroidManifest.xml must additionally declare the service inside <application> (a <service> subclass can't be auto-injected):

<service android:name="io.mob.background.BeamForegroundService"
    android:exported="false"
    android:foregroundServiceType="dataSync" />

The BeamForegroundService source ships in this package under priv/native/android/BeamForegroundService.kt — copy it into your app's host package (the build copies only the bridge automatically).

Stop behaviour

stop/0 sends ACTION_STOP to the service, which calls stopForeground and stopSelf. The OS removes the notification immediately. If the BEAM node goes silent (no incoming distribution traffic) the OS may still eventually kill the process — keep_alive/0 prevents aggressive background process killing but not an eventual idle OOM kill after many hours of complete inactivity.

Android 15 time limit

With targetSdk 35+, Android allows a dataSync foreground service at most 6 hours per 24 hours while the app is in the background. When the limit is reached the system calls the service's onTimeout, which logs a warning (tag MobBackground) and stops the service, so the app does not crash. The BEAM is not notified. Call keep_alive/0 again once the app is back in the foreground.

Summary

Functions

Starts the keep-alive (silent audio session on iOS, foreground service on Android) so the OS does not suspend the app when the screen locks. Idempotent — safe to call more than once.

Stops the keep-alive and allows the OS to suspend the app normally when it goes to background.

Functions

keep_alive()

@spec keep_alive() :: :ok

Starts the keep-alive (silent audio session on iOS, foreground service on Android) so the OS does not suspend the app when the screen locks. Idempotent — safe to call more than once.

stop()

@spec stop() :: :ok

Stops the keep-alive and allows the OS to suspend the app normally when it goes to background.