如何检测OS X应用程序是否已启动

时间:2009-03-26 08:14:45

标签: macos boost resources macos-carbon launch

通常,OS X上的应用程序包只能启动一次,但只需复制捆绑包即可启动两次相同的应用程序。检测和阻止这种可能性的最佳策略是什么?

在Windows上,这种效果可以通过应用程序在启动时创建命名资源,然后在无法创建命名资源时退出,表明正在运行已创建相同资源的另一个进程来实现。当应用程序退出时,这些资源在Windows上以可靠的方式发布。

我在研究这个问题时遇到的问题是OS X上的API会在文件系统中保持状态,从而使得在Windows上使用的策略不可靠,即在不正确的退出后延迟文件可能会错误地指示应用程序已在运行

我可以用来在OS X上实现相同效果的API是:posix,carbon和boost。

想法?

9 个答案:

答案 0 :(得分:27)

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];
    }
}

有关详细信息,请参阅http://blog.jseibert.com/post/1167439217/deduplicating-running-instances-or-how-to-detect-if

答案 1 :(得分:8)

低级解决方案是使用flock()。

每个实例都会尝试在启动时锁定文件,如果锁定失败,则另一个实例已在运行。当您的程序退出时,会自动释放Flocks,因此不必担心过时的锁定。

请注意,无论您选择何种解决方案,都需要有意识地决定拥有“多个实例”的含义。具体来说,如果多个用户同时运行您的应用,那可以吗?

答案 2 :(得分:8)

有一个神秘的Info.plist密钥叫做#34;应用程序禁止多个实例,"但它似乎并不适合我。我正在编写一个CLI应用程序并从一个包中执行它。也许它可以在GUI应用程序中运行,但我还没有尝试过。

答案 3 :(得分:4)

正如已经提到的,Cocoa应用程序通常不允许您一次运行多个实例。

一般来说,一个cocoa方法可以解决这个问题,看看NSWorkspace中的launchApplications。这将返回一个NSArray,其中包含每个已启动应用程序的字典。您可以遍历数组以查看您要查找的应用程序是否已在运行。我建议您使用值为NSApplicationBundleIdentifier的值,该值将具有“com.mycompany.myapp”之类的值,而不是查找名称。如果您需要查找应用程序的软件包标识符,可以查看应用程序包中的info.plist文件。

答案 4 :(得分:3)

首先,它是“Mac OS X”或“OS X”。没有“OS / X”这样的东西。

其次,Mac OS X没有附带Boost;你需要将它与你的应用程序捆绑在一起。

第三,大多数碳不能用于64位。这是一个明确的信号,即某些部分的碳将在某一天消失(当苹果放弃其硬件中的32位时)。迟早,你将不得不用Cocoa重写你的应用程序或放弃Mac。

  

通常情况下,OS / X上的应用程序包只能启动一次,但只需重命名捆绑包就可以启动两次相同的应用程序。

不,不能。启动重命名或移动的应用程序只会激活(带到前面)已经运行的进程;它不会在第一个过程中开始新的第二个过程。


有几种方法可以判断应用程序是否已在运行。在每种情况下,您都会在启动时执行此操作:

  1. 使用Cocoa的NSConnection注册具有单个常量名称的连接。如果名称已经注册,则会失败。 (您可以使用Carbon应用程序中的Foundation;它是您必须小心使用的Application Kit。)
  2. 使用Process Manager扫描进程列表,查找其bundle标识符与您要查找的进程匹配的进程。捆绑标识符不可更改,但更改比文件名或位置更难。
  3. 如果您想查看有人自己运行第二份副本,您可以使用CFNotificationCenter:

    1. 将自己添加为“com.yourdomain.yourappname.LaunchResponse”的观察员。
    2. 以“com.yourdomain.yourappname.LaunchCall”名称发布通知。
    3. 将自己添加为“com.yourdomain.yourappname.LaunchCall”的观察员。
    4. 在您的观察呼叫回叫中,发布响应通知 在您的回应通知的观察回调中,退出。

      因此,当第一个进程启动时,它将调用并得不到响应;当第二个进程开始时,它将调用,从第一个进程获得响应,然后以第一个进程的顺序退出。

答案 5 :(得分:1)

IPC怎么样?您可以打开套接字并与其他已启动的实例进行协商。但是,你必须要小心,如果两个应用程序同时启动它会起作用。

我无法为您提供示例代码,因为我还没有(但我会很快)使用它。

答案 6 :(得分:1)

这是Romans和Jeff对Swift 2.0的回答的组合:如果具有相同捆绑ID的另一个应用实例已经在运行,则显示警报,激活另一个实例并退出重复实例。

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)
    }

    /* ... */
}

答案 7 :(得分:1)

这是 Swift 3.0 的seb版本:如果具有相同软件包ID的另一个应用程序实例已在运行,请显示警报,激活另一个实例并退出重复实例。

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)
         }   
       /* ... */
}

答案 8 :(得分:0)

检测具有相同bundleID的应用程序是否正在运行,激活它并关闭开始的内容。

- (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;
    }