"adb start-server",Java,Gradle和apache-commons-exec:如何使其正确?



当我试图从Java内部作为外部进程运行android start-server时,遇到了各种问题。Java由Gradle调用。让我向你描述一下在各种情况下到底发生了什么:

环境

  • Windows 7 X64
  • Java 7
  • commons-exec-1.1
  • 1.6级
  • 安卓API 17
  • IntelliJ IDEA 12.1.4社区版

假设
adb守护进程被终止,并将在调用adb start-server时启动。

案例1

此代码:

DefaultExecutor executor = new org.apache.commons.exec.DefaultExecutor();
executor.execute(org.apache.commons.exec.CommandLine.parse("adb start-server"));
log.info("Checkpoint!");

当从应用程序插件的Gradle run任务运行时,将显示启动服务器输出,即:

* daemon not running. starting it now on port 5037 *
* daemon started successfully *

然后它将挂起,即永远不会记录"Checkpoint!"。手动终止adb.exe进程将导致代码继续执行。

问题1

为什么这个调用被阻止?当adb start-server命令从终端运行时,几秒钟后控制权就会返回到终端,那么为什么代码中没有发生这种情况呢?

案例2

如果我直接使用Java运行时,如下所示:

Runtime.getRuntime().exec(new String[]{"adb", "start-server"});
log.info("Checkpoint!");
System.exit(0);

如果像以前一样从Gradle调用,则会记录"Checkpoint!"。但是,执行将挂在System.exit(0)上。手动终止adb.exe将再次使Gradle调用完成。

在这种情况下,不显示adb start-server的输出。

有趣的是,当我运行IntelliJ IDEA的应用程序时,而不是Gradle,构建设置模仿Gradle的构建设置,一切都很好,应用程序也能正常完成。

问题2

为什么Gradle挂在System.exit(0)上而IntelliJ不挂?这在某种程度上与Gradle本身是一个内部调用Java的进程有关吗?在IntelliJ的情况下,Java是立即调用的,没有任何间接调用?为什么这很重要?

问题3

最终,我希望能够在没有任何挂起的情况下从Gradle运行此程序。adb start-server的日志输出将是一个额外的奖励。如果有任何关于如何做到这一点的提示,我将不胜感激。

如果我答错了问题,请原谅,但如果我想从gradle启动服务器(我们这样做是为了tomcat、sonicMQ、ApacheDS和EnterpriseDB),我会从gradleExec任务开始,只有在它不充分的情况下才尝试其他任务。

task startADB(type: Exec) {
    commandLine 'adb', 'start-server'
}

不幸的是,我无法回答您关于各种代码挂起的任何问题,因为我一无所知!我只知道,如果我想从gradle启动服务器,我会这么做。事实上,我就是这么做的。另一个稍微复杂一点的选项是使用tanuki包装器或类似的东西。你仍然可以从gradle开始和停止它,但你会得到一些高级功能。

最终我通过以下操作解决了问题:

  1. 在我的程序中,对于调用adb start-server,我不再使用org.apache.commons.exec.DefaultExecutor.execute("adb start-server"),也不再使用Java的Runtime.getRuntime().exec("adb start-server")
    相反,我使用java.lang.ProcessBuilder("adb start-server").inheritIO().start().waitFor()。注意,inheritIO()是在Java7中添加的,它允许在线读取已启动的进程stdout等
    EDITGradle中的inheritIO()在直接从CLI调用Gradle时不起作用,而不是由IntelliJ IDEA调用。有关详细信息,请参阅此问题。这里描述的实现StreamGobbler可能会解决这个问题。

  2. 在Gradle中,我再次使用ProcessBuilder重用run任务变量,而不是Gradle的应用程序插件的run任务。看起来是这样的:

apply plugin: 'java'
apply plugin: 'application'
sourceCompatibility = '1.7'
targetCompatibility = '1.7'
// configuration-time of the original run task
run { 
    main = com.example.MainClass; // without this, our custom run will try to run "null"
}
task myRun(dependsOn: build) {
        // execution-time of our custom run task.
        doFirst {
            ProcessBuilder pb = new ProcessBuilder(tasks['run'].commandLine);
            pb.directory(tasks['run'].workingDir);
            // works when the gradle command is executed from IntelliJ IDEA, has no effect when executed from standalone CLI interface.
            pb.inheritIO(); 
            Process proc = pb.start();
            proc.waitFor();
        }
    }

至于这些奇怪行为的原因,我没有任何确切的答案,所以我不做进一步的评论。

2013年7月9日编辑
这个问题似乎指出了问题2的答案:在windows上,父进程在终止之前等待子进程。不幸的是,由于问题1中所述的问题,在那里提出的解决方案不起作用。

希望这能有所帮助,
Konrad

最新更新