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

Posted

技术标签:

【中文标题】如何检测 OS X 应用程序是不是已经启动【英文标题】:How to detect whether an OS X application is already launched如何检测 OS X 应用程序是否已经启动 【发布时间】:2010-10-15 15:44:14 【问题描述】:

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

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

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

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

想法?

【问题讨论】:

你为什么还要这样做?与 Windows 不同,操作系统负责防止应用程序的多个实例在常见情况下运行。在不常见的情况下,为什么要阻止它? 有问题的应用程序是一个游戏。通过在一台机器上运行多个游戏副本,玩家在某些情况下会比其他玩家拥有不公平的优势。 【参考方案1】:

这在 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。

【讨论】:

在 OS 10.8 中,在 main.m 开始时执行此检查不起作用,因为在该阶段正在运行的应用程序本身尚未在数组中(可能仅在下一个 runloop 或所以),所以你必须检查“> 0”而不是“> 1”。为了安全起见w.r.t。未来版本,最好显式检查当前应用的数组:for (NSRunningApplication* runningApp in runningApplications) if (![runningApp isEqual:[NSRunningApplication currentApplication]]) // Alert and exit 一个更重要的问题:runningApplicationsWithBundleIdentifier 返回与 bundleID 匹配的正在运行的应用程序,但至关重要的是只返回当前用户拥有的应用程序(因此这些解决方案不会阻止这台机器上的不同用户在同一时间。 11 年后,仍然是最简单最好的解决方案! MacOS 蒙特雷【参考方案2】:

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

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

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

【讨论】:

谢谢,该解决方案会很好。锁定文件将针对每个用户,以不阻止同一台计算机上的多个用户同时启动应用程序。【参考方案3】:

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

【讨论】:

此键 (LSMultipleInstancesProhibited) 在从 Launchpad 或 Finder 启动应用程序时效果很好。作为奖励,已经运行的应用程序被带到了前面。对我来说,这比显示错误对话框要好。从命令行启动应用程序时,该键不起作用。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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

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

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

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


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

    使用 Cocoa 的 NSConnection 注册一个具有单个常量名称的连接。如果名称已经注册,这将失败。 (您可以通过 Carbon 应用使用 Foundation;这是您必须小心使用的 Application Kit。) 使用进程管理器扫描进程列表以查找其包标识符与您要查找的进程匹配的进程。包标识符并非不可更改,但它比文件名或位置更难更改。

    如果您想查看某人何时运行您自己的第二个副本,您可以使用 CFNotificationCenter:

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

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

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

【讨论】:

我认为他的意思是复制而不是重命名。无论如何,您可以使用“open -n TextEdit.app”打开第二个实例 或者启动 -m,如果你安装了 Nicholas Riley 的启动。【参考方案6】:

这是 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】:

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

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

【讨论】:

请注意不要破坏应用程序同时在多个用户下运行的能力。当另一个用户已经在使用它时退出的应用程序已损坏。【参考方案8】:

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

【讨论】:

当应用程序由不同的用户运行时,这似乎不起作用。在这种情况下,NSRunningApplication.runningApplications 不会返回其他用户的实例。【参考方案9】:

检测具有相同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;
    

【讨论】:

以上是关于如何检测 OS X 应用程序是不是已经启动的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Mac OS X 中检测是不是连接了蓝牙音频设备?

如何检测 OS X 是不是处于暗模式?

WinForm检测用户是不是已登陆的方法--简单问题--请高手回答!

如何在浏览器 JavaScript 中检测 OS X 是不是处于暗模式? [复制]

如何检测在 OS X 上生成特定核心转储的应用程序?

苹果 OS X制作u盘启动盘