How Bitchat Android Maintains Background Relay Connectivity

Bitchat Android keeps Nostr relay WebSocket connections alive in the background using a singleton NostrRelayManager coupled with Android foreground services, background ownership tagging, and automatic retry logic.

The Bitchat Android app implements a sophisticated relay connectivity system that ensures persistent Nostr protocol communication even when the app is not actively visible. According to the permissionlesstech/bitchat-android source code, this is achieved through tight integration between connection management, Android service lifecycles, and privacy-preserving network routing.

Core Architecture: NostrRelayManager

The foundation of background relay connectivity lives in app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt. This singleton manager maintains all WebSocket connections to Nostr relays and provides lifecycle-aware connection ownership.

Background Ownership Model

The manager distinguishes between UI-tied and persistent connections using an owner tagging system:

// From NostrRelayManager.kt line 33
const val OWNER_BACKGROUND = "background"

When a relay is created with OWNER_BACKGROUND, the manager treats it as a long-running connection that survives UI lifecycle events. The manager stores active Relay objects—each wrapping an OkHttp WebSocket—and prevents garbage collection of background-owned sockets.

Background Runtime Initialization

The NostrBackgroundRuntime class ensures relay connectivity starts early in the application lifecycle:

// From NostrBackgroundRuntime.kt line 53
val manager = NostrRelayManager.getInstance(context)
manager.addRelay(
    url = "wss://relay.example.com",
    owner = NostrRelayManager.OWNER_BACKGROUND
)

This initialization pattern runs from the Application class, guaranteeing background relays establish before any UI component requests them.

Foreground Service: MeshService

Android's background execution restrictions make foreground services essential for persistent sockets. Bitchat implements MeshService in app/src/main/java/com/bitchat/android/mesh/MeshService.kt:

  • Starts automatically when NostrRelayManager detects active background relays
  • Runs with persistent notification, qualifying as a foreground process under Android's classification
  • Prevents OS killing while WebSocket connections remain open
  • Stops self-terminating when no relays require maintenance

The service also coordinates with BluetoothMeshService for hybrid mesh-Nostr operation.

Automatic Reconnection and Retry Logic

WebSocket failures trigger structured recovery through RetryingControlPacketSender. This component:

  1. Listens for onFailure and onClosed callbacks on each Relay WebSocket
  2. Schedules exponential backoff retries respecting Android background limits
  3. Surfaces diagnostics through the same pattern as DebugSettingsManager (lines 157-228)

The debug UI in DebugSettingsSheet exposes relay statistics—packet counts, round-trip times, and connection states—helping diagnose background connectivity health.

Tor Integration and Connection Reset

For privacy-focused users, ArtiTorManager in app/src/main/java/com/bitchat/android/net/ArtiTorManager.kt maintains connectivity across Tor circuit changes:

// From ArtiTorManager.kt line 335
NostrRelayManager.shared.resetAllConnections()

This forced reconnection ensures all relays establish fresh sockets over the new Tor circuit, preventing identity correlation across circuits.

Graceful Shutdown Coordination

Clean termination prevents resource leaks and dangling sockets. AppShutdownCoordinator in app/src/main/java/com/bitchat/android/service/AppShutdownCoordinator.kt handles this:

// From AppShutdownCoordinator.kt line 59
NostrRelayManager.shared.disconnect()

The coordinator iterates all active relays, closes each WebSocket, and stops MeshService—ensuring no background process survives app closure.

Practical Implementation Example

class BitchatApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Initialize background relay connectivity
        val relayManager = NostrRelayManager.getInstance(this)
        
        // Add persistent relay for location-based notes
        relayManager.addRelay(
            url = "wss://relay.bitchat.network",
            owner = NostrRelayManager.OWNER_BACKGROUND
        )
        
        // Ensure connection established
        relayManager.ensureConnected(NostrRelayManager.OWNER_BACKGROUND)
    }
}

// After Tor circuit restart
ArtiTorManager.restartTor {
    NostrRelayManager.shared.resetAllConnections()
}

Key Source Files

File Responsibility
nostr/NostrRelayManager.kt Singleton WebSocket management and retry logic
nostr/NostrBackgroundRuntime.kt Application-scoped background relay initialization
mesh/MeshService.kt Foreground service preventing OS process termination
net/ArtiTorManager.kt Tor circuit coordination and forced reconnections
service/AppShutdownCoordinator.kt Clean relay disconnection on app termination
ui/debug/DebugSettingsManager.kt Connectivity diagnostics and statistics collection

Summary

  • Background relay connectivity relies on NostrRelayManager with OWNER_BACKGROUND tagging to persist connections outside UI lifecycle
  • Foreground service (MeshService) qualifies the process as foreground, preventing Android from killing active relay sockets
  • Automatic retry logic handles WebSocket failures with exponential backoff and background-aware scheduling
  • Tor integration forces connection resets on circuit changes to maintain privacy guarantees
  • Graceful shutdown through AppShutdownCoordinator ensures clean resource release

Frequently Asked Questions

Why does Bitchat need a foreground service for relay connectivity?

Android's background execution restrictions introduced in API 26+ limit background services and network access for implicit processes. The MeshService foreground service runs with a visible notification, which Android classifies as a foreground process—allowing WebSocket connections to remain active indefinitely without being killed for memory reclamation.

How does Bitchat handle relay disconnections automatically?

Each Relay object registers OkHttp WebSocket callbacks. When onFailure or onClosed fires, RetryingControlPacketSender schedules reconnection attempts with exponential backoff. This retry mechanism respects Android's background job limitations while ensuring eventual recovery without user intervention.

What happens to relays when the app closes completely?

AppShutdownCoordinator intercepts application termination and calls NostrRelayManager.shared.disconnect(). This iterates all active relays, closes each WebSocket cleanly, and stops MeshService—preventing zombie connections, notification persistence, and battery drain from orphaned background processes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →