当我试图从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开始和停止它,但你会得到一些高级功能。
最终我通过以下操作解决了问题:
-
在我的程序中,对于调用
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
可能会解决这个问题。 -
在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