通常,你不会遇到这个问题,直到你需要写一个后台程序……
我们都知道 macOS 有一个全局的 Dock,这个东西上会显示所有正在运行的程序,但如果你的程序是一个菜单栏小程序,或者是一个……输入法,那你肯定不想让这个 app 的图标显示的 Dock 上,因为这类 app 是要持续在后台运行的。
这时候我们就可以在 Info.plist 中写 LSBackgroundOnly 字段,这样这个 app 就会在后台运行,没有 Dock 图标,也不会在 cmd+tab 的切换选项中显示出来了。落格输入法就是这么做的,这也是苹果官方示例中的配置——但这么做长久以来伴随着一个问题,那就是如果你这个 app 还是需要显示必要的设置界面的话,那这个界面会永远在所有窗口的最下方,也就是说,窗口一弹出就立即被挡住了。甚至,除了标准窗口,其他任何警告弹窗,都是无法显示的,除非先有一个标准窗口出现,且用户点击了一下该窗口的任何位置……
上文这些前提导致了很多问题,我不得不内嵌另外一个 app 来作为落格输入法的专门设置软件,然后两者之间使用 xpc 通信,真的是令人绝望。
直到今天 TIL,我发现原来可以使用 NSApplication.shared.setActivationPolicy(.accessory) 在程序中动态改变自己的运行级别,可以动态修改自己为普通 regular 模式,或者是后台 prohibited 模式。显然,通过看代码名称也明白了,原来这个后台模式,是故意不允许弹出窗口的。
1 2 |
/* The application does not appear in the Dock and may not create windows or be activated. This corresponds to LSBackgroundOnly=1 in the Info.plist. This is also the default for unbundled executables that do not have Info.plists. */ case prohibited = 2 |
显然,如果你声明程序是后台程序,那它的一切窗口都是不能获得焦点的,这个问题我一直以来以为是 macOS 的Bug😅
那么,有没有别的方案呢?还真的有,除了上文的两种模式外,macOS 其实还提供了介于两者中间的模式,既像后台程序那样不显示图标,又能在必要的时候获取焦点和类似普通程序那样弹窗:
1 2 |
/* The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows. This corresponds to LSUIElement=1 in the Info.plist. */ case accessory = 1 |
在必要的时候动态切换程序的运行模式为 accessory 即可。
当然,如果用代码太麻烦,我们也可以直接改 Info.plist,把 LSBackgroundOnly ,改为 LSUIElement ,即可。
本文由 落格博客 原创撰写:落格博客 » macOS application 的三种运行模式
转载请保留出处和原文链接:https://www.logcg.com/archives/3531.html
博主您好 我现在遇到了这个问题:正常的逻辑是,我点击主界面窗口的关闭按钮,然后DOCK消失,但是弹出一个新窗口,提示进入后台运行。使用了NSApplication.sharedApplication().setActivationPolicy(0x1),但是这串代码执行之后,我所有的窗口都不见了,请问如何解决。
我使用的是JAVA的SWT框架。
我没用过 SWT 框架,不过你试试用alert代替窗口,或者设置窗口为固定最前,就那个popup级别。理论上不应该出现这个情况。
另外你检查一下传入 0x1 是否正确,这个我不确定 SWT 如何转换传值的,有可能转成了无法识别的值于是出现意外行为。
目前在M1处理器 13.1上不会出现问题,在intel 10.15.7版本上,我先执行setActivationPolicy(0x1),然后弹窗(就是一个窗口),出现后立马消失。然后我循环判断setActivationPolicy(0x1)的返回值,当返回值为true的时候,弹窗将正常。是不是因为setActivationPolicy(0x1)底层是多线程,导致他在我弹窗生成后才执行。(但是即使是这样,应该也是将弹窗置于最下面,而不是直接一闪而过)
哦哦,那确实,这类API都不是实时生效的,你试试等待个一秒,应该就没问题了。我确实遇到过类似的问题。
请问有没有更好的解决方案,因为用户的设备可能很差,也可能很好,我无法确定等待的时间
有一个思路就监测app运行状态,比如进入后台之类的? 或者看看获取这个状态的API的结果,生效后再弹窗就是了。虽然我搞清楚了这个情况,后来由于其他原因,我并没有用到,我的输入法设置窗口还是使用了单独的app实现的。
感谢您的解答,您的这个思路确实有效。但是我有一个疑问,监测APP状态的时候,有没有可能,app已经关闭了,但是setActivationPolicy还没有执行完毕(目前没有遇到)。那么我判断app状态是关闭的时候,打开了弹窗,随后setActivationPolicy执行,那么我的弹窗将再次被其关闭。
所以我新开了一个线程,判断setActivationPolicy的返回值是不是true,是的话,说明他执行结束了,我就打开弹窗。这样我的弹窗将一直在setActivationPolicy后执行,不会被关闭。
还有一个疑问就是,我不知道为什么在我的程序中,执行setActivationPolicy(1)后,会关闭我所有的窗口,他的效果不应该是关闭DOCK,并且将所有窗口置于最下面吗?
是隐藏……他只是不显示了,并不是给你关闭了。
另外设置为1是既隐藏又允许显示,所以窗口的开关只能你自己控制。
你这个情况🤔我也不清楚,抱歉了哈。
请问 :LSBackgroundOnly 在 Info.plist 中如何输写
直接在根层级添加一个字段,key就是 LSBackgroundOnly,类型为bool,值为1或者yes