繁体   English   中英

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

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

我正在用LibGDX制作游戏,现在我试图通过重新运行jar重新启动游戏,因为我正在使用jar的路径,并通过以下方式找到它:

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

使用完之后,我尝试使用Process
Runtime.getRuntime().exec("java -jar " + location + "\\\\Test.jar");
现在可以正常工作了,但是问题在于,我用来创建新实例的游戏的第一个实例(从中重新启动)仍然保留在屏幕上,直到第二个实例关闭后才关闭。
这是我的重启代码:

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();
    }
}

难道我做错了什么? 启动第二个实例后如何关闭游戏的第一个实例?
我尝试使用另一个线程来执行此操作,并包含以下代码:

    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();
    }
}

但是它仍然有同样的问题。

编辑:我试图使用System.exit(0); ,并尝试使用LwjglApplication将其关闭,但它保持不变,但是我取得了一些进展:
创建此新进程时,游戏的第二个实例,第一个实例的UI冻结,导致游戏不响应。 我认为,好吧,如果它不响应,我应该找到一种杀死它并离开另一个实例的方法,但是由于关闭游戏的一个实例(通过强制关闭)这一事实无法实现。 ),您同时关闭两个实例。
我想我想出了一个令人讨厌的难题:
假设我们的主要游戏实例为“ Game_1”,而我们正在创建的实例为“ Game_2”。
在查看了代码并考虑了会发生什么之后(通过测试小类而不是大型游戏),我认为“ Game_1”没有关闭,因为“ Game_2”没有关闭。
用更复杂的术语来说,“ Game_1”的实例不会关闭,因为它以某种方式附加到“ Game_2”,因此在“ Game_2”关闭之前会等待自身关闭。
因此,如果正确,关闭“ Game_1”的方式将使“ Game_2”与“ Game_1”同时运行,从而使其独立,从而允许“ Game_1”继续执行当前的代码进度,这将是Gdx.app.exit();的实现Gdx.app.exit(); 方法。
所以现在问题仍然存在,如何使“ Game_2”的实例独立于“ Game_1”运行? 或者,我将如何使“ Game_1”继续执行代码,或者不等到从“ Game_2”收到退出值。
EDIT2: 大规模进展添加一行代码System.exit(0); 在重新启动类中,“ Game_1”继续不响应, 但是在终止“ Game_1”之后,“ Game_2”没有关闭,我将继续尝试直到找到解决方法。
EDIT3:我继续尝试对其进行修复,以便可以正常工作,但是遇到了另一个问题。 我发现,如果我可以模拟'Game_2'的退出值而不实际退出,那么我可以终止'Game_1'的UI,同时保持游戏2的状态,如果有人有任何想法请与我分享。
EDIT4:我继续尝试执行此操作,但是我无法跟踪正在发生的事情,我正在尝试通过编写将PID传递给重新启动类的方法
"java -cp " + location + "\\\\Test.jar Restart " + PID但是它似乎不起作用,或者我似乎没有从Restart类接收任何信息(例如,syso)。 最重要的是,我发现我的游戏内存泄漏,一旦解决,就会解决。
请,如果您有任何帮助我的想法,甚至只是理论,请分享一下。
EDIT5:我已经使用此LINK确定了终止给定过程的效率



有一种“更轻松”的方法可以执行您想要的操作。 当然,您将不得不适应自己的应用程序,因为您尝试做的事情完全超出了libgdx的范围。 这是一个跨平台的库,更新/重新启动的想法与移动设备大不相同。

可以在此处找到实际的桌面跨平台解决方案,我强烈建议您不要使用您的方法,因为它不是可靠的解决方案,并且与平台有关。

以下是如何在libgdx中执行此操作的示例。 您需要两件事,启动应用程序的代码和重新启动应用程序的代码。

发射器:

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();
        }
    }
}

示例游戏代码:

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();
        }
    }
}

这是解决方案,因为直到明天我才能回答我的问题:

好了,最后,我完成了它的解决,它有几个问题,我只提到其中两个,因为它涉及的是一般代码,而不是我的使用方式。 “ Game_1”将是首先启动的游戏,而“ Game_2”将是重新启动的游戏的实例。 就是这个:
首先,我得到了当前正在运行的当前进程的PID,即“ Game_1”,从中我将创建“ Game_2”。 这样做的问题是Java应用程序都具有相同的名称“ Java.exe”,并且导致一堆具有相同名称的应用程序,因此,我现在添加一条消息,指出游戏应该是唯一的Java实例,而不是日食或类似的东西。
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";
}

现在,稍后,我将寻找一种方法来命名当前正在运行的进程,这样您就不必使用line.contains("java")因为它可能会给出多个实例,但现在它是要多好能有多好。
此代码在Windows内部使用了一个exe文件,该文件基本上提供了计算机上正在运行的所有当前进程,因此您可以找到自己的文件。
返回的列表以以下格式给出:

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

PID位于第27个字符到第35个字符之间,因此我添加了
String data = line.subSequence(27, 35).toString();
以便它返回进程的PID。
之后,我准备了一个带有执行命令的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 + "\"");

现在首先我得到了.jar文件的位置。 它以以下格式返回:

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

因此,位置必须采用以下格式

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

只是将所有%20都转换为空格。
注意如果目录中没有空格,则无需进行格式化的前一步骤。
之后,我准备了实际的命令,如下所示(这是给我的,但是您可以根据需要进行更改)。
java在cmd中调用Java程序。
-cp执行位于jar文件内部的类。
然后,我添加了jar位置,然后添加了程序包,并添加了要终止的PID的参数(对于main方法中的String[] args )。
现在,以下代码行表示一个操作系统依赖性,因此,如果要添加多个操作系统支持,我建议在另一个操作系统中找到与cmd等效的内容,并弄清楚如何使用它。
代码的最后一行是执行,在此我们获得运行时,启动cmd并在关闭cmd之前执行一条命令。
您可以在以下问题中找到有关它的详细信息: LINK
@Vincent Ramdhanie还提供了指向激活cmd时可以使用运行时运行的命令的链接。
之后,我有一个实际上是在重新启动游戏的类,称为Restart。
像最后一行代码一样,其中的一行代码表示操作系统依赖性,因此,如果要支持多个操作系统, taskkil在其他操作系统中找到与taskkil等效的代码。 据@erhun它pkill为Linux或什么的,对不起,我不完全记得。
这是该类的代码:

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();
    }
}

与上一行相同,此处的位置与以前相同,如果目录中有空格,则必须对其进行格式化。
之后,您需要终止先前的过程,即taskkill /F /PID + args[0]进入的位置。如果运行,您将终止ID为args [0]的任务,该ID为'Game_1'' PID。
之后,我只运行了jar文件,您一切顺利。
我想说明一下,我尝试运行它,以便主类(DesktopLauncher)在运行时通过exec命令使用Restart类,但是问题仍然存在,并且我发现解决此问题的唯一方法是正常工作在它周围,并使用cmd。 (这是在使用cmd调试位置字符串之后)。
就是这样,我工作了整整一周的时间,试图解决这个问题,但就目前而言,这虽然很粗糙,但它是一个解决方案。 如果我在此代码中的某处存在问题,请告诉我。

暂无
暂无

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

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