22

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

在 Windows 上,这种效果可以简单地通过应用程序在启动时创建命名资源来实现,然后如果无法创建命名资源则退出,这表明另一个进程正在运行,并且已经创建了相同的资源。当应用程序退出时,这些资源会在 Windows 上以可靠的方式释放。

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

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

想法?

4

9 回答 9

30

这在雪豹中非常容易:

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

于 2010-09-22T15:14:19.000 回答
9

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

每个实例都会在启动时尝试锁定文件,如果锁定失败,则另一个实例已经在运行。当你的程序退出时,Flock 会自动释放,所以不用担心过时的锁。

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

于 2009-03-26T11:33:11.180 回答
8

有一个名为“应用程序禁止多个实例”的神秘 Info.plist 键,但它似乎对我不起作用。我正在编写一个 CLI 应用程序并从一个包中执行它。也许它可以在 GUI 应用程序中工作,但我还没有尝试过。

于 2012-03-08T01:58:00.737 回答
4

如前所述,Cocoa 应用程序通常不允许您一次运行多个实例。

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

于 2009-07-04T22:11:11.057 回答
3

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

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

第三,大多数 Carbon 在 64 位中不可用。这是一个明确的信号,即 Carbon 的这些部分有一天会消失(当 Apple 在其硬件中放弃 32 位时)。迟早,你要么用 Cocoa 重写你的应用程序,要么放弃 Mac。

通常,OS/X 上的应用程序包只能启动一次,但是通过简单地重命名包,相同的应用程序可以启动两次。

不,它不能。启动重命名或移动的应用程序将简单地激活(带到前面)已经运行的进程;它不会与第一个进程一起启动新的第二个进程。


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

  1. 使用 Cocoa 的 NSConnection 注册一个具有单个常量名称的连接。如果名称已经注册,这将失败。(您可以从 Carbon 应用程序中使用 Foundation;这是您必须小心使用的应用程序工具包。)
  2. 使用进程管理器扫描进程列表以查找其包标识符与您要查找的进程匹配的进程。包标识符不是不可更改的,但它比文件名或位置更难更改。
  3. 如果您想查看某人何时运行您自己的第二个副本,您可以使用 CFNotificationCenter:

    1. 将自己添加为“com.yourdomain.yourappname.LaunchResponse”的观察者。
    2. 以“com.yourdomain.yourappname.LaunchCall”的名称发布通知。
    3. 将自己添加为“com.yourdomain.yourappname.LaunchCall”的观察者。

    在呼叫通知的观察回调中,发布响应通知。
    在响应通知的观察回调中,退出。

    因此,当第一个进程启动时,它将调用并没有得到响应;当第二个进程启动时,它将调用,从第一个进程获取响应,然后退出第一个进程。

于 2009-03-26T08:39:35.763 回答
2

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

    /* ... */
}
于 2015-10-03T11:14:04.943 回答
1

工控机呢?您可以打开一个套接字并与另一个启动的实例进行协商。但是,您必须小心,如果两个应用程序同时启动,它就会起作用。

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

于 2009-03-26T08:39:10.117 回答
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)
         }   
       /* ... */
}
于 2017-10-10T16:05:59.033 回答
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;
    }
于 2014-05-21T06:23:24.717 回答