2009-03-26 23 views
19

Normalmente un pacchetto di applicazioni su OS X può essere avviato solo una volta, tuttavia, semplicemente copiando il pacchetto, la stessa applicazione può essere avviata due volte. Qual è la migliore strategia per rilevare e fermare questa possibilità?Come rilevare se un'applicazione OS X è già stata avviata

Su Windows questo effetto può essere semplicemente ottenuto dall'applicazione che crea una risorsa denominata all'avvio e quindi esce se non è possibile creare la risorsa denominata, indicando che è in esecuzione un altro processo che ha già creato la stessa risorsa. Queste risorse vengono rilasciate in modo affidabile su Windows quando si chiude l'applicazione.

Il problema che ho visto quando la ricerca di questo è che le API su OS X mantenere lo stato nel file system e quindi rende la strategia utilizzata sulle finestre inaffidabili, cioè persistente file dopo un'uscita impropria può erroneamente indicare che l'applicazione è già in esecuzione.

Le API che posso utilizzare per ottenere lo stesso effetto su OS X sono: posix, carbon e boost.

Idee?

+0

Perché vuoi farlo anche tu? A differenza di Windows, il sistema operativo si occupa di impedire l'esecuzione di più istanze di un'applicazione nel caso comune. Nel caso raro, perché impedirlo? –

+0

L'applicazione in questione è un gioco. Eseguendo più copie del gioco su una singola macchina, un giocatore avrebbe un vantaggio ingiusto sugli altri giocatori in alcune situazioni. –

risposta

8

Una soluzione di basso livello è utilizzare flock().

Ogni istanza tenta di bloccare un file all'avvio e se il blocco non riesce, un'altra istanza è già in esecuzione. I greggi vengono rilasciati automaticamente al termine dell'esecuzione del programma, quindi non preoccuparti delle serrature obsolete.

Nota che qualsiasi soluzione tu scelga, devi prendere una decisione consapevole su cosa significa avere "istanze multiple". In particolare, se più utenti eseguono la tua app nello stesso momento, va bene?

+0

Grazie, questa soluzione andrà bene. I file di blocco saranno per utente per non bloccare più utenti sullo stesso computer per avviare l'app nello stesso momento. –

1

Che dire di IPC? È possibile aprire un socket e negoziare con l'altra istanza avviata. Dovresti fare attenzione però, che funzioni se entrambe le app iniziano nello stesso momento.

Non riesco a fornirvi codice di esempio, poiché non l'ho ancora usato (ancora, ma lo farò presto).

+2

Fai attenzione a non rompere la capacità della tua app di eseguire contemporaneamente più utenti. Un'applicazione che si chiude quando un altro utente lo sta già utilizzando è danneggiata. –

3

Prima di tutto, è "Mac OS X" o "OS X". Non esiste una cosa come "OS/X".

Secondo, Mac OS X non viene fornito con Boost; avresti bisogno di raggrupparlo con la tua applicazione.

In terzo luogo, la maggior parte di Carbon non è disponibile in 64 bit. Questo è un chiaro segnale che quelle porzioni di Carbon andranno via un giorno (quando Apple abbandona 32 bit nel suo hardware). Prima o poi, dovrai riscrivere la tua app con Cocoa o abbandonare il Mac.

Normalmente un pacchetto di applicazioni su OS/X può essere avviato solo una volta, tuttavia, semplicemente rinominando il pacchetto, la stessa applicazione può essere avviata due volte.

No, non è possibile. Avviare l'applicazione rinominata o spostata semplicemente attiverà (porta in primo piano) il processo che era già in esecuzione; non inizierà un nuovo, secondo processo insieme al primo.


Ci sono diversi modi per sapere se un'applicazione è già in esecuzione. In ogni caso, lo si fa al momento del lancio:

  1. Utilizzare NSConnection di Cocoa per registrare una connessione con un singolo nome costante. Questo fallirà se il nome è già registrato. (È possibile utilizzare Foundation da un'app Carbon, è necessario utilizzare il kit di applicazione.)
  2. Utilizzare Process Manager per eseguire la scansione dell'elenco dei processi per i processi il cui identificatore di bundle corrisponde a quello che si sta cercando. L'identificatore del pacchetto non è immodificabile, ma è più difficile da modificare rispetto al nome file o alla posizione.
  3. Se stai cercando di vedere quando qualcuno corre una seconda copia di te stesso, è possibile utilizzare CFNotificationCenter:

    1. aggiungere te stesso come un osservatore per “com.yourdomain.yourappname.LaunchResponse”.
    2. Invia una notifica sotto il nome "com.yourdomain.yourappname.LaunchCall".
    3. Aggiungi te stesso come osservatore per "com.yourdomain.yourappname.LaunchCall".

    Nella richiamata osservazione per la notifica di chiamata, inviare la notifica di risposta.
    Nella richiamata osservazione per la notifica Risposta, uscita.

    Così, quando inizia il primo processo, chiamerà e non riceverà risposta; quando inizia il secondo processo, chiamerà, riceverà una risposta dal primo processo e uscirà in deferenza al primo.

+0

Penso che intendesse copiare invece di rinominare. Ad ogni modo, puoi aprire una seconda istanza usando "open -n TextEdit.app" –

+0

Oppure avviare -m, se hai installato il lancio di Nicholas Riley. –

3

Come già accennato, le applicazioni Cocoa di solito non consentono di eseguire più di un'istanza alla volta.

In generale, un modo di cacao per risolvere questo aspetto in LaunchApplications in NSWorkspace. Questo restituisce un NSArray contenente un dizionario per ciascuna applicazione avviata. Puoi scorrere l'array per vedere se l'app che stai cercando è già in esecuzione. Ti consiglio di utilizzare il valore con la chiave NSApplicationBundleIdentifier che avrà un valore come "com.mycompany.myapp" piuttosto che cercare il nome. Se è necessario trovare l'identificatore del bundle per un'app, è possibile consultare il suo file info.plist nel pacchetto dell'app.

24

Ciò è estremamente facile in Snow Leopard:

- (void)deduplicateRunningInstances { 
    if ([[NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]] count] > 1) { 
     [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Another copy of %@ is already running.", [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey]] 
         defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"This copy will now quit."] runModal]; 

     [NSApp terminate:nil]; 
    } 
} 

Vedi http://blog.jseibert.com/post/1167439217/deduplicating-running-instances-or-how-to-detect-if per ulteriori informazioni.

+2

In OS 10.8, fare questo controllo all'avvio in main.m non funziona perché in quella fase l'app in esecuzione non è ancora nell'array (probabilmente viene registrata solo nel prossimo runloop o giù di lì), quindi invece di " > 1 "dovresti controllare per"> 0 ". Per giocare a questo sicuro w.r.t. versioni future, è meglio controllare esplicitamente l'array per l'app corrente: 'for (NSRunningApplication * runningApp in runningApplications) { if (! [runningApp isEqual: [NSRunningApplication currentApplication]]) { // Avviso e uscita }}' –

+2

Un altro importante problema: runningApplicationsWithBundleIdentifier restituisce le applicazioni in esecuzione che corrispondono a un bundleID, ma fondamentalmente solo quelle di proprietà dell'utente corrente (quindi queste soluzioni non impediranno a utenti diversi su questa macchina di eseguire l'app nello stesso momento) –

7

C'è una misteriosa chiave Info.plist chiamata "L'applicazione proibisce più istanze", ma non sembra funzionare per me. Sto scrivendo un'applicazione CLI e l'esecuzione da all'interno di un pacchetto. Forse funzionerebbe in un'applicazione GUI, ma non ho provato.

+4

Questa chiave (LSMultipleInstancesProhibited) funziona bene quando l'app viene lanciata da Launchpad o da Finder. Come bonus, l'app già in esecuzione viene portata in primo piano, per me è meglio visualizzare una finestra di errore La chiave non funziona quando l'app viene avviata dal comando linea. – Maf

0

rileva se l'applicazione con lo stesso bundleID è in esecuzione, attivarla e chiudere l'avvio.

- (id)init method of <NSApplicationDelegate> 

    NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]]; 
    if ([apps count] > 1) 
    { 
     NSRunningApplication *curApp = [NSRunningApplication currentApplication]; 
     for (NSRunningApplication *app in apps) 
     { 
      if(app != curApp) 
      { 
       [app activateWithOptions:NSApplicationActivateAllWindows|NSApplicationActivateIgnoringOtherApps]; 
       break; 
      } 
     } 
     [NSApp terminate:nil]; 
     return nil; 
    } 
1

Si tratta di una combinazione di risposte romani e Jeff, da Swift 2.0: Se un'altra istanza dell'applicazione con lo stesso ID fascio è già in esecuzione, mostrano un avviso, attivare l'altra istanza e chiudere l'istanza duplicato.

func applicationDidFinishLaunching(aNotification: NSNotification) { 
    /* Check if another instance of this app is running. */ 
    let bundleID = NSBundle.mainBundle().bundleIdentifier! 
    if NSRunningApplication.runningApplicationsWithBundleIdentifier(bundleID).count > 1 { 
     /* Show alert. */ 
     let alert = NSAlert() 
     alert.addButtonWithTitle("OK") 
     let appName = NSBundle.mainBundle().objectForInfoDictionaryKey(kCFBundleNameKey as String) as! String 
     alert.messageText = "Another copy of \(appName) is already running." 
     alert.informativeText = "This copy will now quit." 
     alert.alertStyle = NSAlertStyle.CriticalAlertStyle 
     alert.runModal() 

     /* Activate the other instance and terminate this instance. */ 
     let apps = NSRunningApplication.runningApplicationsWithBundleIdentifier(bundleID) 
     for app in apps { 
      if app != NSRunningApplication.currentApplication() { 
       app.activateWithOptions([.ActivateAllWindows, .ActivateIgnoringOtherApps]) 
       break 
      } 
     } 
     NSApp.terminate(nil) 
    } 

    /* ... */ 
} 
0

Questa è una versione di SEB di per Swift 3.0: se un'altra istanza dell'applicazione con lo stesso ID fascio è già in esecuzione, mostrano un avviso, attivare l'altra istanza e chiudere l'istanza duplicato.

func applicationDidFinishLaunching(aNotification: NSNotification) { 
    /* Check if another instance of this app is running. */ 
    let bundleID = Bundle.main.bundleIdentifier! 
    if NSRunningApplication.runningApplications(withBundleIdentifier: bundleID).count > 1 { 
     /* Show alert. */ 
     let alert = NSAlert() 
     alert.addButton(withTitle: "OK") 
     let appName = Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String 
     alert.messageText = "Another copy of \(appName) is already running." 
     alert.informativeText = "This copy will now quit." 
     alert.alertStyle = NSAlert.Style.critical 
     alert.runModal() 

     /* Activate the other instance and terminate this instance. */ 
     let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) 
      for app in apps { 
        if app != NSRunningApplication.current { 
         app.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) 
         break 
        } 
      } 
       NSApp.terminate(nil) 
     } 
     /* ... */ 
} 
Problemi correlati