Nucleus
Lifecycle

Une app, un seul process

Garantis une seule instance et redirige les relances (args CLI, deep links) vers elle.

Double-clique deux fois sur le app et la plupart des utilisateurs s'attendent à ce que le second clic ramène la fenêtre au premier plan, pas qu'il lance un second process. SingleInstanceManager en fait le comportement par défaut — et donne un moyen propre de passer des arguments de l'invocation secondaire vers la primaire.

TL;DR

  • Fourni avec nucleus-applicationnucleusApplication { } acquiert le verrou pour soi par défaut.
  • Ou appelle SingleInstanceManager.isSingleInstance(...) à la main si l'on n'utilises pas nucleusApplication { }.
  • Basé sur un file-lock : portable macOS / Windows / Linux — pas de LaunchServices / mutex / DBus à câbler.
  • Deux callbacks laissent l'instance secondaire écrire un payload (args CLI, URI deep link) que la primaire lit atomiquement.
  • L'identifiant par défaut vient de NucleusApp.appId injecté par le plugin Gradle.

Installation

Inclus dans core-runtime, dont nucleus-application dépend déjà.

dependencies {
    implementation("dev.nucleusframework:nucleus.core-runtime:<version>")
}

Quickstart

Avec nucleusApplication { } — activé d'office :

import dev.nucleusframework.application.nucleusApplication

fun main(args: Array<String>) = nucleusApplication(args, enableSingleInstance = true) {
    DecoratedWindow(onCloseRequest = ::exitApplication) { /* ... */ }
}

À la main, quand on câble le propre application { } :

import dev.nucleusframework.core.runtime.SingleInstanceManager

fun main() = application {
    var restoreRequested by remember { mutableStateOf(false) }

    val isSingle = remember {
        SingleInstanceManager.isSingleInstance(
            onRestoreFileCreated = {
                // Sur la SECONDE instance — `this` est le chemin du fichier IPC.
                // Écris ce qu'on veut que la primaire lise.
            },
            onRestoreRequest = {
                // Sur la PRIMAIRE quand un autre lancement est détecté.
                restoreRequested = true
            },
        )
    }

    if (!isSingle) {
        exitApplication()
        return@application
    }

    Window(onCloseRequest = ::exitApplication) {
        LaunchedEffect(restoreRequested) {
            if (restoreRequested) {
                window.toFront(); window.requestFocus()
                restoreRequested = false
            }
        }
    }
}

Comment ça marche

Un fichier de verrou vit dans le dossier temp (configurable via Configuration.lockFilesDir). L'acquisition passe par java.nio.channels.FileLock — atomique, géré par le noyau, libéré automatiquement à la sortie ou au crash de la JVM. Si le verrou est déjà tenu, le secondaire écrit un fichier "restore request" à côté ; le primaire surveille ce chemin et déclenche onRestoreRequest.

Ça t'évite trois API spécifiques à chaque OS : LaunchServices sur macOS (un bundle id, un process), un named mutex Windows, l'ownership d'un nom DBus sur Linux. Un seul mécanisme partagé marche partout, et surtout survit aux crashes JVM — les file locks sont relâchés par l'OS, donc un primaire tué de force ne bloque pas tout le monde.

Les deux callbacks portent l'IPC. Ils reçoivent le même Path (le fichier restore-request), donc côté secondaire on y écris quelque chose (typiquement un URI deep-link via DeepLinkHandler.writeUriTo), côté primaire on le relis. Plus qu'un URI ? Utiliser ce path comme point de rendez-vous pour le format qu'on veut.

Référence

SingleInstanceManager

MembreSignatureNotes
isSingleInstance(onRestoreFileCreated, onRestoreRequest)(Path.() -> Unit, Path.() -> Unit) -> BooleanRenvoie true si l'on est la primaire, false sinon.
configurationvar ConfigurationSurcharge le dossier / l'identifiant avant le premier appel.

Configuration

PropriétéDéfautDescription
lockFilesDirjava.io.tmpdirOù vivent les fichiers de verrou et de restore-request.
lockIdentifierNucleusApp.appIdIdentifiant unique — dérive le nom du fichier de verrou.
lockFileName / restoreRequestFileNameDérivésÀ surcharger uniquement si l'on veut des noms fixes.

Notes

  • nucleusApplication { enableSingleInstance = false } désactive le verrou.
  • Combine avec DeepLinkHandler pour transférer les URL monapp://... vers la primaire.
  • Le fichier de verrou est par utilisateur (temp dir), donc deux users distincts peuvent faire tourner l'app en parallèle.