Package 

Object OneSignalDispatchers


  • 
    public class OneSignalDispatchers
    
                        

    Optimized threading manager for the OneSignal SDK.

    Performance optimizations:

    • Lazy initialization to reduce startup overhead

    • Custom thread pools for both IO and Default operations

    • Optimized thread pool configuration (smaller pools)

    • Small bounded queues (10 tasks) to prevent memory bloat

    • Reduced context switching overhead

    • Efficient thread management with controlled resource usage

    Made public to allow mocking in tests via IOMockHelper.

    • Method Detail

      • prewarm

         final Unit prewarm()

        Triggers the lazy initialization of IO, Default, and SerialIO (and their backing executors, dispatchers, and scopes) on a short-lived background thread.

        Background: The lazy by lazy properties below construct ThreadPoolExecutor instances and wrap them in asCoroutineDispatcher() + SupervisorJob() + CoroutineScope(...). Production remote-logging shows that when the first caller of launchOnIO / launchOnSerialIO is on the main thread (Activity-lifecycle handler, JobService.onStartJob, etc.), the construction cost — which includes a kotlinx.coroutines.BuildersKt.launch that hits ThreadPoolExecutor.execute and LinkedBlockingQueue.offer synchronously — is paid on the calling thread, blocking the main thread for many seconds on cold start.

        Calling prewarm from a non-time-sensitive spot in com.onesignal.OneSignal.initWithContext shifts that cost to a dedicated OneSignal-prewarm daemon thread, so the first production caller — including main-thread lifecycle handlers — only pays the much cheaper "submit work to an already-constructed executor" cost.

        Idempotent and fire-and-forget: a no-op on second and subsequent calls. Safe to invoke from any thread; the heavy lifting always happens on the daemon thread we spawn here, not on the caller. Failures are logged and swallowed because the executors retain their existing fallback paths (e.g. Dispatchers.IO.limitedParallelism(1) for SerialIO) and a failed prewarm will simply mean the first production caller pays the original cost.

        Best-effort, not a hard guarantee. Because prewarm is fire-and-forget, a caller that dispatches immediately afterward can still win the lazy-init race and pay construction on its own thread. The fix relies on placing prewarm at cold-start entry points where there is meaningful lead time (e.g. a goAsync() handoff or initWithContext work) before the first suspendify* / launchOn* dispatch.

        suspendifyOnIO, suspendifyOnDefault, launchOnIO, launchOnDefault, and suspendifyOnSerialIO always route through IO / Default / SerialIO, so prewarm benefits the very first real dispatch from any of them.

        The known main-thread cold-start entry points: | Entry point | Class | |---|---| | process-start activity lifecycle registration | core.internal.application.impl.ActivityLifecycleInitializer | | initWithContext / initWithContextSuspend | OneSignalImp | | onStartJob | core.services.SyncJobService | | onReceive | FCMBroadcastReceiver, NotificationDismissReceiver, BootUpReceiver, UpgradeReceiver | | onMessage / registration callbacks | ADMMessageHandler, ADMMessageHandlerJob | | onNewToken / onMessageReceived | OneSignalHmsEventBridge | | processIntent / processOpen | NotificationOpenedActivityBase, NotificationOpenedActivityHMS |

        When adding a new cold-start entry point (receiver, job, activity trampoline, push bridge), call prewarm at the top of it before the first dispatch.

      • getIO

         final CoroutineDispatcher getIO()
      • getDefault

         final CoroutineDispatcher getDefault()