简体   繁体   English

使用Runtime.exec()后关闭线程的GUI

[英]Closing a Thread's GUI after using Runtime.exec()

I am making a game with LibGDX, now I am trying to restart the game by re-running the jar, for that I am using the jar's path, finding it by using: 我正在用LibGDX制作游戏,现在我试图通过重新运行jar重新启动游戏,因为我正在使用jar的路径,并通过以下方式找到它:

String location = new File(DesktopLauncher.class
                    .getProtectionDomain().getCodeSource().getLocation()
                    .getPath()).toString().replace("%20", " ");

After using that I attempt to restart using a Process and the 使用完之后,我尝试使用Process
Runtime.getRuntime().exec("java -jar " + location + "\\\\Test.jar");
Now that far it works, but the problem is that the first instance of the game from which I create the new instance (from which I restart), remains on the screen and won't close until the second instance closes. 现在可以正常工作了,但是问题在于,我用来创建新实例的游戏的第一个实例(从中重新启动)仍然保留在屏幕上,直到第二个实例关闭后才关闭。
This is my code for the restart: 这是我的重启代码:

public static void restart() {
    Gdx.app.exit();
    try {
        String location = new File(DesktopLauncher.class
                .getProtectionDomain().getCodeSource().getLocation()
                .getPath()).toString().replace("%20", " ");
        System.out.println(location);
        Process pro = Runtime.getRuntime().exec(
                "java -jar " + location + "\\Test.jar");
        BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
                "reprot.txt")));
        InputStream stream = pro.getErrorStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                stream));
        String line = "";
        writer.write(location);
        while ((line = reader.readLine()) != null) {
            writer.write(line);
        }
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Am I doing something wrong? 难道我做错了什么? How can I close the first instance of the game after starting the second instance? 启动第二个实例后如何关闭游戏的第一个实例?
I tried doing it using a different thread, having this code: 我尝试使用另一个线程来执行此操作,并包含以下代码:

    public static void main(String[] args) {
    try {

        String location = new File(DesktopLauncher.class
                .getProtectionDomain().getCodeSource().getLocation()
                .getPath()).toString();
        System.out.println(location);
        Process pro = Runtime.getRuntime().exec(
                "java -jar " + location + "\\Test.jar");
        BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
                "report.txt")));
        InputStream stream = pro.getErrorStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                stream));
        String line = "";
        writer.write(location);
        while ((line = reader.readLine()) != null) {
            writer.write(line);
        }
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

But it still has the same problem. 但是它仍然有同样的问题。

EDIT: I tried to used System.exit(0); 编辑:我试图使用System.exit(0); , and try to use the LwjglApplication to close it but it remains the same, however I have made some progress: ,并尝试使用LwjglApplication将其关闭,但它保持不变,但是我取得了一些进展:
When creating this new process, the second instance of the game, the first instance's UI freezes, causing the game not to respond. 创建此新进程时,游戏的第二个实例,第一个实例的UI冻结,导致游戏不响应。 I thought, well if it doesn't respond I should just find a way to kill it off and leave the other instance, but that can't be accomplished due to the fact that when closing once instance of the game (by forcing a close on it), you close both instances. 我认为,好吧,如果它不响应,我应该找到一种杀死它并离开另一个实例的方法,但是由于关闭游戏的一个实例(通过强制关闭)这一事实无法实现。 ),您同时关闭两个实例。
I think I figured out a piece of this annoying puzzle: 我想我想出了一个令人讨厌的难题:
Let's say our main instance of the game is called 'Game_1', and the instance that we're creating is 'Game_2'. 假设我们的主要游戏实例为“ Game_1”,而我们正在创建的实例为“ Game_2”。
After looking at the code and thinking about what happens (with testing of small classes and not the large game), I think that that the 'Game_1' isn't closing because 'Game_2' isn't closing. 在查看了代码并考虑了会发生什么之后(通过测试小类而不是大型游戏),我认为“ Game_1”没有关闭,因为“ Game_2”没有关闭。
In more complex terms, the instance of 'Game_1' won't close because it is somehow attached to 'Game_2' and thus is waiting for 'Game_2' to close before it itself will close. 用更复杂的术语来说,“ Game_1”的实例不会关闭,因为它以某种方式附加到“ Game_2”,因此在“ Game_2”关闭之前会等待自身关闭。
So if that correct, the way to close 'Game_1' would to make 'Game_2' run simultaneously to 'Game_1' making it independent and thus allowing 'Game_1' to continue with the current progress of the code, which will be the implementation of Gdx.app.exit(); 因此,如果正确,关闭“ Game_1”的方式将使“ Game_2”与“ Game_1”同时运行,从而使其独立,从而允许“ Game_1”继续执行当前的代码进度,这将是Gdx.app.exit();的实现Gdx.app.exit(); method. 方法。
So now the question remains, how do I make the instance of 'Game_2' run independently from 'Game_1'? 所以现在问题仍然存在,如何使“ Game_2”的实例独立于“ Game_1”运行? Or how would I make 'Game_1' continue the code or, not to wait till an exit value will be received from 'Game_2'. 或者,我将如何使“ Game_1”继续执行代码,或者不等到从“ Game_2”收到退出值。
EDIT2: MASSIVE PROGRESS After adding a line of code System.exit(0); EDIT2: 大规模进展添加一行代码System.exit(0); in the restart class, 'Game_1' continued not to respond, BUT after terminating 'Game_1', 'Game_2' did not get turned off, I'll continue to play around with it until I figure out what to do. 在重新启动类中,“ Game_1”继续不响应, 但是在终止“ Game_1”之后,“ Game_2”没有关闭,我将继续尝试直到找到解决方法。
EDIT3: I continue to try and fix it so it'll work, but have ran into another problem. EDIT3:我继续尝试对其进行修复,以便可以正常工作,但是遇到了另一个问题。 I figured out that if I can simulate an exit value for the process of 'Game_2' without actually exiting, I can terminate 'Game_1' 's UI, while keeping game 2 still alive, if anyone has any ideas please share them with me. 我发现,如果我可以模拟'Game_2'的退出值而不实际退出,那么我可以终止'Game_1'的UI,同时保持游戏2的状态,如果有人有任何想法请与我分享。
EDIT4: I continue my attempts to do this, but I can't follow what's going on, I'm trying to pass a PID to the restart class by writing EDIT4:我继续尝试执行此操作,但是我无法跟踪正在发生的事情,我正在尝试通过编写将PID传递给重新启动类的方法
"java -cp " + location + "\\\\Test.jar Restart " + PID but it doesn't seem to work, or I don't seem to receive any information (syso for example) from the Restart class. "java -cp " + location + "\\\\Test.jar Restart " + PID但是它似乎不起作用,或者我似乎没有从Restart类接收任何信息(例如,syso)。 On top of that I have found a memory leak inside my game that I will address once I finish working this out. 最重要的是,我发现我的游戏内存泄漏,一旦解决,就会解决。
Please, if you have any idea how to help me, even just a theory, please share it. 请,如果您有任何帮助我的想法,甚至只是理论,请分享一下。
EDIT5: I have established the efficiency of the termination of a given process using this LINK EDIT5:我已经使用此LINK确定了终止给定过程的效率



There is a much 'easier' method to do what you want. 有一种“更轻松”的方法可以执行您想要的操作。 You will of course have to adapt to your own application as what you are trying to do is completely outside of libgdx's scope. 当然,您将不得不适应自己的应用程序,因为您尝试做的事情完全超出了libgdx的范围。 It is a cross-platform library and the idea update/restart is very different with mobile. 这是一个跨平台的库,更新/重新启动的想法与移动设备大不相同。

An actual desktop cross-platform solution can be found here , I would highly suggest you not use your method as it is not a reliable solution and very platform specific. 可以在此处找到实际的桌面跨平台解决方案,我强烈建议您不要使用您的方法,因为它不是可靠的解决方案,并且与平台有关。

Below is an example of how you would do it in libgdx. 以下是如何在libgdx中执行此操作的示例。 You need two things, code to launch the application and code to restart it. 您需要两件事,启动应用程序的代码和重新启动应用程序的代码。

Launcher: 发射器:

public class TestLauncher {
    public static void main(final String[] args) {
        final LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
        cfg.title = "Game";
        cfg.width = 1280;
        cfg.height = 720;
        cfg.backgroundFPS = 12;
        cfg.foregroundFPS = 60;

        final Runnable rebootable = new Runnable() {
            @Override public void run() {
                if (Gdx.app != null) {
                    Gdx.app.exit();
                }
                TestLauncher.restart();
            }
        };
        new LwjglApplication(new RebootTest(rebootable), cfg);
    }

    public static void restart() {
        final StringBuilder cmd = new StringBuilder();
        cmd.append(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java ");
        for (final String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
            cmd.append(jvmArg + " ");
        }
        cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
        cmd.append(TestLauncher.class.getName()).append(" ");

        try {
            Runtime.getRuntime().exec(cmd.toString());
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }
}

Sample Game Code: 示例游戏代码:

public class RebootTest implements ApplicationListener {
    private final Runnable rebootHook;
    private Stage stage;
    private Skin skin;

    public RebootTest(final Runnable rebootHook) {
        this.rebootHook = rebootHook;
    }

    @Override public void create() {
        this.stage = new Stage();
        this.skin = new Skin(Gdx.files.internal("skin/uiskin.json"));

        final Table table = new Table();
        table.setFillParent(true);

        final TextButton button = new TextButton("Reboot", this.skin);
        button.addListener(new ClickListener() {
            @Override public void clicked(final InputEvent event, final float x, final float y) {
                Gdx.app.postRunnable(RebootTest.this.rebootHook);
            }
        });

        table.add(button).expand().size(120, 40);

        this.stage.addActor(table);

        Gdx.input.setInputProcessor(this.stage);
    }

    @Override public void resize(final int width, final int height) {}

    @Override public void render() {
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        this.stage.act();
        this.stage.draw();
    }

    @Override public void pause() {}

    @Override public void resume() {}

    @Override public void dispose() {
        if (this.stage != null) {
            this.stage.dispose();
        }
        if (this.skin != null) {
            this.skin.dispose();
        }
    }
}

Here is the solution, since I can't answer my problem until tomorrow: 这是解决方案,因为直到明天我才能回答我的问题:

Alright, finally, I finished solving it, it has a few problems, only two of them I will mention since it concerns the code in general and not how I'm using it. 好了,最后,我完成了它的解决,它有几个问题,我只提到其中两个,因为它涉及的是一般代码,而不是我的使用方式。 'Game_1' will be the game that was started first, and 'Game_2' will be the instance of the restarted game. “ Game_1”将是首先启动的游戏,而“ Game_2”将是重新启动的游戏的实例。 This is it: 就是这个:
First off I got the PID of the current process that is currently running, 'Game_1', from which I will create 'Game_2'. 首先,我得到了当前正在运行的当前进程的PID,即“ Game_1”,从中我将创建“ Game_2”。 The problem with this is that Java applications all have the same name, 'Java.exe', and what that causes is a bunch of applications of the same name, so for now I add a message saying that the game should be the only java instance on the computer, not eclipse, or anything like that. 这样做的问题是Java应用程序都具有相同的名称“ Java.exe”,并且导致一堆具有相同名称的应用程序,因此,我现在添加一条消息,指出游戏应该是唯一的Java实例,而不是日食或类似的东西。
The code for the PID retrieval is this: PID检索的代码是这样的:

private static String getPID() {
    try {
        String line;
        Process p = Runtime.getRuntime().exec(
                System.getenv("windir") + "\\system32\\" + "tasklist.exe");
        BufferedReader input = new BufferedReader(new InputStreamReader(
                p.getInputStream()));
        while ((line = input.readLine()) != null) {
            System.out.println(line);
            if (line.contains("java")) {
                String data = line.subSequence(27, 35).toString();
                data = data.trim();
                return data;
            }
        }
        input.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return "-1";
}

Now, later on, I will look for a way to name the process that is currently running, so that you won't have to use line.contains("java") since it might give more than one instance, but for now it's as good as it gets. 现在,稍后,我将寻找一种方法来命名当前正在运行的进程,这样您就不必使用line.contains("java")因为它可能会给出多个实例,但现在它是要多好能有多好。
This code uses an exe file inside of windows that basically gives all the current processes running on the computer, so you can find your. 此代码在Windows内部使用了一个exe文件,该文件基本上提供了计算机上正在运行的所有当前进程,因此您可以找到自己的文件。
The returned list is given in this format: 返回的列表以以下格式给出:

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
All the processes will be located here.

The PID is located between the 27th character to the 35th, and that's why I added PID位于第27个字符到第35个字符之间,因此我添加了
String data = line.subSequence(27, 35).toString();
so that it returns the PID of the process. 以便它返回进程的PID。
After doing that I prepared a cmd with an execution command as follows: 之后,我准备了一个带有执行命令的cmd,如下所示:

String jarLocation = new File(YourClass.class.getProtectedDomain().getCodeSource().getLocation().getPath()).toString();
String command = "java -cp " + jarLocation + " your.Package.here.Restart \""+PID+"\"";
Runtime.getRuntime().exec("cmd /C start cmd.exe /C \"" + command + "\"");

Now first off I got the location of the .jar file. 现在首先我得到了.jar文件的位置。 It is returned in the following format: 它以以下格式返回:

C:\A%20Folder\To%20YourJar\YourJar.jar

So there needs to be the following formatting to the location 因此,位置必须采用以下格式

jarLocation = jarLocation.replace("%20", " ");

Just to turn all the %20's to white spaces. 只是将所有%20都转换为空格。
Note If you do not have spaces in your directory the previous step of formatting is not required. 注意如果目录中没有空格,则无需进行格式化的前一步骤。
After that I had prepared the actual command, which is as follows (this is for me, but you can change it to fit your needs). 之后,我准备了实际的命令,如下所示(这是给我的,但是您可以根据需要进行更改)。
java - calling the java program in cmd. java在cmd中调用Java程序。
-cp - execute a class located inside of a jar file. -cp执行位于jar文件内部的类。
Then I added the jar location, then added the package and added an argument (for the String[] args in the main method) of the PID to be terminated. 然后,我添加了jar位置,然后添加了程序包,并添加了要终止的PID的参数(对于main方法中的String[] args )。
Now the following line of code represents a OS dependency, so if you want to add multiple OS support, I would recommend finding the equivalent to cmd in the other OS and figuring out how to use it. 现在,以下代码行表示一个操作系统依赖性,因此,如果要添加多个操作系统支持,我建议在另一个操作系统中找到与cmd等效的内容,并弄清楚如何使用它。
The last line of code is the execution, where we get the runtime, start a cmd and execute a single command before closing the cmd. 代码的最后一行是执行,在此我们获得运行时,启动cmd并在关闭cmd之前执行一条命令。
You can find details about it in the following issue: LINK 您可以在以下问题中找到有关它的详细信息: LINK
@Vincent Ramdhanie also gives a link to commands you can run using runtime when activating cmd. @Vincent Ramdhanie还提供了指向激活cmd时可以使用运行时运行的命令的链接。
After that I had a class that was actually restarting the game itself, which is named Restart. 之后,我有一个实际上是在重新启动游戏的类,称为Restart。
Like the last line of code, a line of code there represents OS dependency, so if you want to support multiple OS's, find the equivalent to taskkil in other OS's. 像最后一行代码一样,其中的一行代码表示操作系统依赖性,因此,如果要支持多个操作系统, taskkil在其他操作系统中找到与taskkil等效的代码。 According to @erhun it's pkill for Linux or something, sorry I don't exactly remember. 据@erhun它pkill为Linux或什么的,对不起,我不完全记得。
This is the code for that class: 这是该类的代码:

public static void main(String[] args) {
    try {
        String location = new File(DesktopLauncher.class
                .getProtectionDomain().getCodeSource().getLocation()
                .getPath()).toString();
        location = "\"" + location.replaceAll("%20", " ");
        Runtime.getRuntime().exec("taskkill /F /PID " + args[0]);
        Runtime.getRuntime().exec("java -jar " + location);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Like with the previous line, location here means the same thing as before, and you have to format it if you have spaces in the directory. 与上一行相同,此处的位置与以前相同,如果目录中有空格,则必须对其进行格式化。
After that you need to terminate the previous process, that is where taskkill /F /PID + args[0] comes in. If you run that you will terminate the task with the id of args[0], which was 'Game_1' 's PID. 之后,您需要终止先前的过程,即taskkill /F /PID + args[0]进入的位置。如果运行,您将终止ID为args [0]的任务,该ID为'Game_1'' PID。
After that I just run the jar file and you're good to go. 之后,我只运行了jar文件,您一切顺利。
I would like to note something, I tried running it so that the main class (DesktopLauncher) would use the Restart class through an exec command in runtime, but the problem presisted, and I found that the only way to fix this, was to work around it, and use cmd. 我想说明一下,我尝试运行它,以便主类(DesktopLauncher)在运行时通过exec命令使用Restart类,但是问题仍然存在,并且我发现解决此问题的唯一方法是正常工作在它周围,并使用cmd。 (This was after using cmd to debug the location string). (这是在使用cmd调试位置字符串之后)。
That's it, I worked a whole week, trying to fix this problem, and as crude as this is, it's a solution, for the mean time. 就是这样,我工作了整整一周的时间,试图解决这个问题,但就目前而言,这虽然很粗糙,但它是一个解决方案。 If I have a problem somewhere in this code please tell me. 如果我在此代码中的某处存在问题,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM