2009-03-26 9 views
19

Thông thường, một gói ứng dụng trên OS X chỉ có thể được bắt đầu một lần, tuy nhiên chỉ cần sao chép gói, cùng một ứng dụng có thể được khởi chạy hai lần. Chiến lược tốt nhất để phát hiện và ngăn chặn khả năng này là gì? Trên Windows, hiệu ứng này chỉ đơn giản có thể đạt được bằng ứng dụng tạo tài nguyên được đặt tên khi khởi động và sau đó thoát nếu tài nguyên được đặt tên không thể được tạo ra, chỉ ra rằng một tiến trình khác đang chạy đã tạo ra cùng một tài nguyên. Các tài nguyên này được phát hành một cách đáng tin cậy trên Windows khi ứng dụng thoát. Vấn đề tôi đã thấy khi nghiên cứu này là các API trên OS X giữ trạng thái trong hệ thống tệp và do đó làm cho chiến lược được sử dụng trên các cửa sổ không đáng tin cậy, tức là các tệp kéo dài sau khi thoát không đúng có thể biểu thị sai rằng ứng dụng đã đang chạy.Cách phát hiện xem ứng dụng OS X đã được khởi chạy chưa

Các API tôi có thể sử dụng để đạt được hiệu ứng tương tự trên OS X là: posix, carbon và boost.

Ý tưởng?

+0

Tại sao bạn thậm chí muốn làm điều này? Không giống như Windows, hệ điều hành sẽ chăm sóc ngăn chặn nhiều phiên bản của một ứng dụng chạy trong trường hợp phổ biến. Trong trường hợp không phổ biến, tại sao ngăn chặn nó? –

+0

Ứng dụng được đề cập là trò chơi. Bằng cách chạy nhiều bản sao của trò chơi trên một máy tính, người chơi sẽ có lợi thế không công bằng so với người chơi khác trong một số trường hợp. –

Trả lời

8

Giải pháp cấp thấp là sử dụng đàn().

Mỗi trường hợp sẽ cố gắng khóa tệp khi khởi động và nếu khóa không thành công thì một phiên bản khác đang chạy. Đàn được tự động phát hành khi chương trình của bạn thoát, do đó, không phải lo lắng về khóa cũ.

Lưu ý rằng bất kỳ giải pháp nào bạn chọn, bạn cần đưa ra quyết định có ý thức về ý nghĩa của việc có "nhiều phiên bản". Cụ thể, nếu nhiều người dùng đang chạy ứng dụng của bạn cùng một lúc, thì có ổn không?

+0

Cảm ơn, giải pháp đó sẽ ổn. Các tệp khóa sẽ được cho mỗi người dùng không chặn nhiều người dùng trên cùng một máy để khởi động ứng dụng cùng một lúc. –

1

Còn khoảng IPC thì sao? Bạn có thể mở một ổ cắm và thương lượng với phiên bản khởi chạy khác. Bạn sẽ phải cẩn thận mặc dù, rằng nó hoạt động nếu cả hai ứng dụng bắt đầu cùng một lúc.

Tôi không thể cung cấp cho bạn mã mẫu, vì tôi chưa (nhưng, tôi sẽ sớm) sử dụng nó.

+2

Hãy thận trọng rằng bạn không làm hỏng khả năng chạy ứng dụng của mình trong nhiều người dùng cùng một lúc. Một ứng dụng thoát khi người dùng khác đang sử dụng nó bị hỏng. –

3

Trước hết, đó là “Mac OS X” hoặc “OS X”. Không có thứ như “OS/X”.

Thứ hai, Mac OS X không đi kèm với Boost; bạn sẽ cần phải gói nó với ứng dụng của bạn.

Thứ ba, hầu hết Carbon không có sẵn trong 64 bit. Đây là một tín hiệu rõ ràng rằng những phần của Carbon sẽ biến mất một ngày nào đó (khi Apple từ bỏ 32-bit trong phần cứng của nó). Sớm hay muộn, bạn sẽ phải viết lại ứng dụng của mình với Cocoa hoặc từ bỏ Mac.

Thông thường, một gói ứng dụng trên OS/X chỉ có thể được bắt đầu một lần, tuy nhiên bằng cách đổi tên gói, cùng một ứng dụng có thể được khởi chạy hai lần.

Không thể. Việc khởi chạy ứng dụng đã đổi tên hoặc di chuyển sẽ đơn giản kích hoạt (đưa lên phía trước) quá trình đã chạy; nó sẽ không bắt đầu một quá trình mới, thứ hai cùng với quy trình đầu tiên.


Có một số cách để biết ứng dụng có đang chạy hay không. Trong mỗi trường hợp, bạn thực hiện việc này khi khởi chạy:

  1. Sử dụng NSConnection của Cocoa để đăng ký kết nối với một tên không đổi. Điều này sẽ thất bại nếu tên đã được đăng ký. (Bạn có thể sử dụng Foundation từ ứng dụng Carbon; đó là Bộ Ứng dụng mà bạn phải cẩn thận.)
  2. Sử dụng Trình quản lý Quy trình để quét danh sách quy trình cho các quy trình có mã nhận diện phù hợp với quy trình bạn đang tìm kiếm. Định danh gói không thể thay đổi được, nhưng khó thay đổi hơn tên tệp hoặc vị trí.
  3. Nếu bạn đang tìm kiếm để xem khi nào ai đó chạy một bản sao thứ hai của mình, bạn có thể sử dụng CFNotificationCenter:

    1. Thêm bản thân như một người quan sát cho “com.yourdomain.yourappname.LaunchResponse”.
    2. Đăng thông báo dưới tên “com.yourdomain.yourappname.LaunchCall”.
    3. Tự thêm mình làm người quan sát cho “com.yourdomain.yourappname.LaunchCall”.

    Trong gọi lại quan sát của bạn cho thông báo Cuộc gọi, hãy đăng thông báo Phản hồi.
    Trong gọi lại quan sát của bạn cho thông báo Phản hồi, thoát.

    Do đó, khi quá trình đầu tiên bắt đầu, nó sẽ Gọi và không nhận được phản hồi; khi quá trình thứ hai bắt đầu, nó sẽ Gọi, nhận được một phản hồi từ quá trình đầu tiên, và thoát ra trong sự quan tâm đến đầu tiên.

+0

Tôi nghĩ anh ấy có nghĩa là sao chép thay vì đổi tên. Dù sao, bạn có thể mở một trường hợp thứ hai bằng cách sử dụng "open -n TextEdit.app" –

+0

Hoặc khởi chạy -m, nếu bạn đã cài đặt khởi chạy của Nicholas Riley. –

3

Như đã đề cập, các ứng dụng Cocoa thường không cho phép bạn chạy nhiều hơn một thể hiện tại một thời điểm.

Nói chung, cách ca cao để giải quyết vấn đề này tại các ứng dụng được khởi chạy ở NSWorkspace. Điều này trả về một NSArray chứa một từ điển cho mỗi ứng dụng được khởi chạy. Bạn có thể lặp qua mảng để xem ứng dụng bạn đang tìm đã chạy chưa. Tôi khuyên bạn nên sử dụng giá trị với khóa NSApplicationBundleIdentifier sẽ có giá trị như "com.mycompany.myapp" thay vì tìm kiếm tên. Nếu bạn cần tìm mã định danh gói cho ứng dụng, bạn có thể xem tệp info.plist trong gói ứng dụng.

24

này là cực kỳ dễ dàng trong 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]; 
    } 
} 

Xem http://blog.jseibert.com/post/1167439217/deduplicating-running-instances-or-how-to-detect-if để biết thêm thông tin.

+2

Trong OS 10.8, kiểm tra ngay khi bắt đầu trong main.m không hoạt động vì ở giai đoạn đó ứng dụng đang chạy không nằm trong mảng (có thể chỉ được đăng ký trong lần chạy tiếp theo), thay vì " > 1 "bạn phải kiểm tra"> 0 ". Để chơi w.r.t. an toàn này các phiên bản trong tương lai, tốt nhất là kiểm tra rõ ràng mảng cho ứng dụng hiện tại: 'for (NSRunningApplication * runningApp trong runningApplications) { if (! [runningApp isEqual: [NSRunningApplication currentApplication]]) { // Alert and exit }}' –

+2

Một vấn đề quan trọng hơn: runningApplicationsWithBundleIdentifier trả về các ứng dụng đang chạy khớp với bundleID, nhưng chủ yếu chỉ là những ứng dụng do người dùng hiện đang sở hữu (vì vậy, các giải pháp này sẽ không ngăn người dùng khác chạy trên máy này chạy cùng một ứng dụng của bạn cùng lúc. –

7

Có một khóa Info.plist bí ẩn được gọi là "Ứng dụng cấm nhiều phiên bản", nhưng dường như nó không hoạt động đối với tôi. Tôi đang viết một ứng dụng CLI và thực hiện nó từ bên trong một gói. Có lẽ nó sẽ làm việc trong một ứng dụng GUI, nhưng tôi đã không cố gắng.

+4

Phím này (LSMultipleInstancesProhibited) hoạt động tốt khi ứng dụng được khởi chạy từ Launchpad hoặc Finder.Để tiền thưởng, ứng dụng đã chạy được đưa lên phía trước. Với tôi, điều này tốt hơn khi hiển thị hộp thoại báo lỗi. hàng. – Maf

0

phát hiện xem ứng dụng có cùng bundleID đang chạy, kích hoạt và đóng ứng dụng nào bắt đầu.

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

Đây là một sự kết hợp của câu trả lời La Mã và Jeff cho Swift 2.0: Nếu một thể hiện của ứng dụng với bó cùng một ID đang chạy, hiển thị cảnh báo, kích hoạt ví dụ khác và bỏ các trường hợp trùng lặp.

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

Đây là một phiên bản của SEB cho Swift 3.0: Nếu một thể hiện của ứng dụng với bó cùng một ID đang chạy, hiển thị cảnh báo, kích hoạt ví dụ khác và bỏ các trường hợp trùng lặp.

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