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-application—nucleusApplication { }acquiert le verrou pour soi par défaut. - Ou appelle
SingleInstanceManager.isSingleInstance(...)à la main si l'on n'utilises pasnucleusApplication { }. - 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.appIdinjecté 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
| Membre | Signature | Notes |
|---|---|---|
isSingleInstance(onRestoreFileCreated, onRestoreRequest) | (Path.() -> Unit, Path.() -> Unit) -> Boolean | Renvoie true si l'on est la primaire, false sinon. |
configuration | var Configuration | Surcharge le dossier / l'identifiant avant le premier appel. |
Configuration
| Propriété | Défaut | Description |
|---|---|---|
lockFilesDir | java.io.tmpdir | Où vivent les fichiers de verrou et de restore-request. |
lockIdentifier | NucleusApp.appId | Identifiant unique — dérive le nom du fichier de verrou. |
lockFileName / restoreRequestFileName | Dérivés | À surcharger uniquement si l'on veut des noms fixes. |
Notes
nucleusApplication { enableSingleInstance = false }désactive le verrou.- Combine avec
DeepLinkHandlerpour transférer les URLmonapp://...vers la primaire. - Le fichier de verrou est par utilisateur (temp dir), donc deux users distincts peuvent faire tourner l'app en parallèle.