简体   繁体   中英

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:

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
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); , and try to use the LwjglApplication to close it but it remains the same, however I have made some progress:
When creating this new process, the second instance of the game, the first instance's UI freezes, causing the game not to respond. 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'.
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.
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.
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(); method.
So now the question remains, how do I make the instance of 'Game_2' run independently from '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'.
EDIT2: MASSIVE PROGRESS After adding a line of code 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.
EDIT3: I continue to try and fix it so it'll work, but have ran into another problem. 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.
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
"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. 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



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. 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. 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. 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'. 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.
The code for the PID retrieval is this:

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.
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.
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
String data = line.subSequence(27, 35).toString();
so that it returns the PID of the process.
After doing that I prepared a cmd with an execution command as follows:

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. 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.
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.
-cp - execute a class located inside of a jar file.
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.
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.
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.
You can find details about it in the following issue: LINK
@Vincent Ramdhanie also gives a link to commands you can run using runtime when activating cmd.
After that I had a class that was actually restarting the game itself, which is named 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. According to @erhun it's pkill for Linux or something, sorry I don't exactly remember.
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.
After that I just run the jar file and you're good to go.
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. (This was after using cmd to debug the location string).
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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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