我有一个准备发布的PyQt
应用程序。一切都很顺利,我只剩下一件事要收尾了。我喜欢自动更新的软件:
- 检查url是否有新版本; 发现新版本
- 通知用户更新(点击)→更新
问题是我不知道如何执行此更新。我检查,我找到了新版本,我下载它,然后我必须关闭应用程序并执行新版本的安装程序。如果我关闭它,我就不能执行任何其他操作,如果我执行安装程序,我就不能关闭应用程序。
根据用户的选择,我的程序还下载和安装一些第三方软件,这些软件需要同样的东西:安装程序前关闭,安装程序后重新启动。
下载新版本的安装程序后,您可以使用atexit.register()
和os.exec*()
来运行安装程序,例如atexit.register(os.execl, "installer.exe", "installer.exe")
。这将使安装程序在应用程序即将退出时启动。在调用os.exec*()
之后,应用程序将立即退出,因此不会出现竞争条件。
我喜欢这个公认的答案,但是我有两个建议给你,你可能会考虑使用。有很多文本要消化,但我希望它会很有趣,最重要的是-有帮助。我要写的基本上是两种更新软件的方法,纯粹是基于对其他应用程序的观察。这两种情况都需要重新启动应用程序
-
在运行过程中更换组件-一旦启动,应用程序就会加载到计算机的内存中并驻留在那里。除非它必须以某种方式处理文件系统(例如打开并更改某些配置文件),否则应该能够在应用程序仍在运行时替换这些文件。在Unix/Linux平台上,您可以更改的内容和不可以更改的内容比较严格。一般来说,在Unix/Linux中,一个正在运行的可执行文件不能被更改(出于安全原因),除非您取消它的链接(见这里)。我已经做过几次了,这太大了。如果你不更新可执行文件,你甚至可以避免这一切,并简单地替换其余的文件(配置文件,库等),没有任何问题。更新完成后,可以提示用户重新启动应用程序,以便将新内容加载到内存中。我不确定是否可能,但也许Qt插件基础设施允许添加新的组件,而主要的应用程序仍在运行(没有写很多Qt插件,所以我不知道)。您可以使用被接受的答案所描述的内容进行重新启动,或者继续阅读并应用第二种更新方法的部分内容。
-
使用专用于更新主应用程序的外部进程进行更新Qt有一个良好的基础设施来管理进程。如果你不喜欢它,你总是可以回到Python,它也提供了一种非常相似的做事方式。基本上,我们可以将进程的类型缩小到两种类型对于我们的场景-附加(称为子进程))和detached(简称单机)。在您的情况下,我们可以排除第一种情况,因为-作为术语"子进程";可能告诉你-一旦主进程退出,所有子进程也退出。我们不希望那样。我们想要的是一个分离的进程(稍后您将看到原因)。分离进程的问题在于,它们是分离的。这意味着分离的进程必须能够自己死亡,或者(如果需要的话)您需要恢复对它的控制并由您自己完成。否则进程将继续驻留在您的内存中,这可能不是我们想要的。在您的情况下,外部进程将是您的更新程序(再次在PyQt中编写,一些shell脚本或其他任何允许生成分离进程的东西)。下面是你可以做的(我自己做过,甚至更多,它就像一个魅力):
-
PyQt应用程序已完成在文件夹X中的更新下载(位置应与更新应用程序将寻找新文件的位置一致)
-
生成一个分离的进程
res, pid = QtCore.QProcess.startDetached('YOUR_EXTERNAL_UPDATING_PROGRAM')
时。startDetached()
的工作方式是,如果它成功启动,它返回已启动的外部进程的PID。对于我目前正在编写的应用程序,我实际上需要PID(以便在我的PyQt应用程序死于我的情况下恢复对派生进程的控制),所以我将其存储在一个文本文件中,一旦我的主应用程序再次启动(在崩溃或正常退出后),就会读取该文件。这是我的场景的要求,因为即使UI崩溃,生成的进程也必须保持运行,UI必须恢复到崩溃前的状态(包括控制生成进程的UI控制实体)。您的更新程序不需要这些,因此您可以检查res == True
是否存在(如果进程成功启动,则返回True
)。然而您可能希望将PID存储在应用程序本身调用`QtCore.QCoreApplication.applicationPid()`
You may ask why? Well, because you want to know WHEN your application is no longer running and THEN start the updater (this is a solid insurance that no conflicts will occur when the updater overwrites/remove/renames the application's files including the executable). The easiest but very unreliable way of checking that is to write your updater in such a way that it *waits* for a specific time before starting to tinker with the application's files. The big problem here is that it is not easy to predict how long your application will require to quit and for its data to be flushed from the system's memory. So the other way (there are others but not very reliable) is to store the PID of the application you want to update. Once the updater process has started it will just run a check (in a simple `while` loop) whether the process with PID == 1234 (for example) is still running or not. For that you have plenty of tools including those provided by your platform (see (here)[https://stackoverflow.com/questions/3043978/bash-how-to-check-if-a-process-id-pid-exists] for an example using a shell command). Once the updater makes sure that your application is not running (if the OS lies about it there is nothing we can do about it ;)) it can exit the loop and start the actual update procedure. At this point we can notify the user with a dialog window such as "Your application needs to restart in order to complete an update? [yes]/[no]". If the user choses NO, we can kill the updater process that is running in the background. Otherwise we can quit the application and let the updater do its thing.
Updater更新你的应用程序文件-更新程序现在正在愉快地运行。使用我们应用程序的存储PID,它还确保应用程序的进程不再运行。是时候施展魔法了。在这一点上,你可以做任何你想做的。当然,请记住,您可能需要访问权限来更改文件。如果流程没有以适当的权限启动,它将无法做任何事情。确保这个部门一切正常。您可以生成具有提升权限的进程。这可能需要输入一些密码,在这种情况下,您也必须处理这个。
Updater已经完成对应用程序文件的更新-在所有必需的文件都被修改后,我们不再需要更新程序,我们也想重新启动应用程序。如果菜单上没有重新启动应用程序,也可以跳过此步骤。记住,许多应用程序在更新时提供自动重启,因为它增加了用户体验——用户不必再次手动启动应用程序。你可以让它成为可选的(甚至更好),这绝对更灵活。如果需要重新启动,你基本上可以执行与从应用程序启动更新程序相同的过程,但这次你采用另一种方式-在更新程序中启动应用程序作为一个分离的进程窗体,然后简单地退出更新程序。
-
尽管这是一段很长的文字,但实际实现(尤其是第二个)并不难。
由于其平台依赖性,我没有提到的第三个选项是服务。在Linux、MacOS、Windows等平台上,你有服务。你可以为你的应用程序创建一个更新服务(例如Java的updater服务,它会定期检查新的更新,一旦发现就会提示你)。此外,这与Qt无关,除非你的服务以某种方式使用Qt。
希望这能帮助到一些人。
这就是为什么那么多公司在你的电脑上安装单独的更新服务应用程序。Adobe这么做,Google这么做,似乎每个人都在这么做。避免这种情况的一种方法是让你的应用程序由一个启动器启动,它首先检查主应用程序的更新,如果没有更新,它启动主应用程序,但如果有更新,它首先应用它,然后启动主应用程序。
既然你在使用pyqt,你可以做的另一件事就是在你的应用程序动态加载的python脚本文件中提供一些应用程序的功能。使用py2exe很容易做到这一点。就py2exe而言,将python脚本文件视为绑定的数据文件,在'scripts'或'plugins'文件夹中,并在运行时从文件夹中导入它们。然后,你的应用程序可以检查更新的版本,下载它们,并在加载它们之前更新脚本。