使用 Android Studio 开发 Web 程序 - 调试

调试

这是 “使用 Android Studio 开发 Web 程序” 系列的第三篇文章,接续前一篇文章的内容,源代码照着规格打完了,第一件事当然就是先运行看看成果,如果有出现不符预期的结果,就要进到了调试的程序以便修正源代码。但如果负责的是 Server 端的元件,还要先准备好 Container 的环境、布署编译好的档案、调整设定档等等的程序。过往大部份人可能都会选择 Tomcat 做为 Container,而现在有一个轻量化的选择 Jetty。

Jetty 已在 Gradle 的支持范围内,透过 Gradle 的 Jetty Plugin 来运行 jettyRun Task 就可以直接启动 Jetty,并透过浏览器来检视程序运行后的网页结果,不需要再加外进行安装 Jetty 的动作。

使用 Jetty Plugin 的 build.gradle 档案内容,示范如下:

apply plugin: "war"
apply plugin: "jetty"

dependencies {
   compile "javax.servlet:servlet-api:2.5"
}

httpPort = 8080
stopPort = 9090
stopKey = "stopKey"

以下是使用 Android Studio 的 Terminal 窗口下指令后显示的结果(运行前要先切换到项目所在的文件夹):

如果运行成功,就可以依照结果上出现的网址输入在浏览器里,浏览器会显示程序运行的结果。另外,由于 Android Studio 产生的 Gradle 项目预设会使用 Gradle Wrapper,Wrapper 会被产生在项目的相同文件夹下。运行时是使用名称为 gradlew 的 Wrapper,在 Windows 的平台是批文件、非 Windows 的平台则为 Shell Script,<u>Shell Script 要先设定运行的权限</u>。透过 Wrapper 会自动下载 Gradle 的程序后运行,所以不用先行安装 Gradle,但是如果对 Gradle 版本有特别要求的话,则要改为使用自行安装 Gradle 的方式。

只是要在 IDE 中设断点,并且在浏览器提出要求的过程中触发中断,还需要在命令列中增加以下的参数:

—Dorg.gradle.daemon=true -Dorg.gradle.jvmargs="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"

第一个参数是要 Gradle 以 Daemon 模式运作,Daemon 模式下会重用 Process、减少 Gradle 程序启动的成本,可以加快每一次运行 Gradle 指令时的生成速度。当只有第二个参数被使用时,第一个参数是输出时会提示的选项,同时也是官网建议在进行开发、调试时使用的选项。如果是使用 Android Studio 来启动 Task 则不用特别设定,预设就是使用 Daemon 模式。

第二个参数是开启 JVM 调试功能,让 Debugger 可以透过 5005 Port 与 JVM 建立调试所需的通讯机制。address=5005 所指定的数字可以更改成任意没有被占用的 Port,但必须要与 Debugger 所设定的 Port 相同。

完成了准备的工作后,接下来就要让 Android Studio 的 Debugger 知道调试目标的资讯以便进行调试的工作。点选 Android Studio 的选单功能【Run -> Edit Configurations...】会出现 “Run/Debug Configurations” 窗口。

在 “Run/Debug Configurations” 窗口左上方点选【+】按钮,选择 “Remote” 类型,在下方清单的 Remote 分类就会多出一个项目,设定的画面如下方所示。

所输入的 “Name” 会被显示在 Toolbar 中【Run/Debug Configuration】的下拉菜单中。由于同时启动多个 Configuration 的运行个体会有 Port 被占用的问题,所以勾选了 “Single instance only” 选项。

在 Configuration 选项卡中,上半部是提示要设定 JVM 进行远端调试的参数范例,不同时期的 JDK 有不同的参数规格,每一个范例右方有复制按钮,可以直接复制到剪贴簿中,省去打字的麻烦。

“Settings” 群组内的选项,如果是在开发阶段于本机进行调试可以使用预设的内容。但如果是在系统测试或正式环境中调试,Host 值应该要依据实际的 Server 名称或 IP 位址填入。Port 则是依据 JVM 参数所下的 address 数值做调整。

完成以上的设定,就可以在 Terminal 窗口中运行之前示范并加上参数的指令,待等 Jetty 启动。回到源代码编辑窗口设定好正确的断点位置,接着在 Toolbar 中,选择【Run/Debug Configuration】的下拉清单里刚才设定的 Remote 类型的项目,按下右方 Debug 图标的按钮或 Shift+F9 的快速键。

如果一切顺利,会在 Android Studio 下方的 Console 窗口中看到以下讯息:

Connected to the target VM, address: 'localhost:5005', transport: 'socket'

代表 Debugger 已经成功的和 Jetty 的 JVM 连结上,可以开始进行调试的作业。此时就可以启动浏览器,输入网址,以便运行程序。当运行到所设定的断点就会和其他项目一样,IDE 就会停在断点对应的源代码上,并且可进行观察变量内容、单步执行等操作。

到这里虽然工作已经可以进行了,但是每一次运行调试都要先下一次指令太麻烦了,而且当源代码有修改,则 Jetty 要重启才会运行新的内容。然而先前所下的 gradlew 指令会 Block 住 Terminal,要先按下 Ctrl+C 之后才会关闭 Jetty,也才能再下一次启动 Jetty 的指令。

gradlew 指令会 Block 住的情况如果不消除,对以后自动化生成的作业也会造成问题、导致自动化无法进行。所幸 Jetty Plugin 也有提供 Daemon 参数,可以消除 jettyRun 运行后会 Block 住的情况。为了后续自动化生成也可以一体套用,所以在项目文件夹的 build.gradle 里加上以下的内容:

jettyRun.daemon = true

加完了之后,可以先在 Terminal 试看看,果然可以直接回到提示字元。但这时要记得再运行 jettyStop 的 Task,不然 Jetty 没有关闭,下次运行 jettyRun 时会出现 Port 被占用的错误讯息。

接下来为了确保 jettyRun 在运行前,Jetty 都有确实做关闭的程序,所以要在 build.gradle 里设定 jettyRun 运行之前要先运行 jettyStop。但目前示范使用的 Gradle 2.2.1 有一个 Bug,使用 Daemon 模式的 Jetty 无法被 jettyStop 正常地关闭,参考官方的解决方法后,必须要新增的内容如下:

import org.gradle.api.plugins.jetty.internal.Monitor
[jettyRun, jettyRunWar]*.doLast {
    /**
     * THIS IS A WORKAROUND! THE CURRENT VERSION OF THIS TASK DOESN'T START A WATCHER IN DAEMON MODE
     *
     * If starting the monitor fails, it may be because the jetty task was updated to fix this issue
     * When that happens, we shouldn't need the custom task any more
     *
     * Copied From: AbstractJettyRunTask
     */
    if (getStopPort() != null && getStopPort() > 0 && getStopKey() != null) {
        Monitor monitor = new Monitor(getStopPort(), getStopKey(), server.getProxiedObject());
        monitor.start();
    }
}

[jettyRun, jettyRunWar].each { jetty ->
    jetty.dependsOn jettyStop
}

剩下的就是让之前设定好的 Run/Debug Configuration 与 jettyRun 串连起来,先回到 “Run/Debug Configurations” 窗口,并新增一个 Gradle 类型的 Run/Debug Configuration。在 “Name” 的栏位里输入一个自己可以识别的名称,同时为了保险起见勾选了 “Single instance only” 选项,以避免 Debug Port 被不同 JVM Instance 占住了。

“Gradle project” 栏位直接由 Registered Gradle projects 里挑选对应的 Web 项目,“Tasks” 栏位输入 jettyRun,“VM Option”输入 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=50005,或是由 Remote 类型中复制后贴上。

这里补充说明一下,JVM 的参数也可以设定在 gradle.property 里,gradle.property 可放在以下几个位置:

  • 要套用 JVM 参数的项目其 build.gradle 所在文件夹
  • 登录使用者 Home 路径下的 .gradle 文件夹

但在命令列中指定,将会覆盖过 gradle.property 设定的内容,先前的 VM Option 则等同于在命令列中指定。选择在 Run/Debug Configuration 里设定是因为只有在 IDE 里才有 Debug 的需求,如果设在项目文件夹的 gradle.property 档案内,当档案被 Checkin 到版控系统,可能会被 CI 系统下载进而影响测试或自动化生成的结果。所以为了不影响测试或自动化生成,才会避免在 gradle.property 中进行相关的设定。

最后,点选回到最早建立的 Remote 类型项目,在设定画面的右下方空白清单中指定刚才新增 Gradle 类型项目为 Before execute。完成后,在【Run/Debug Configuration】的下拉清单指定 Remote 类型项目并点选 Toolbar 上的 Debug 图标,如果设定无误就可以看到过程中运行了 jettyStop 及 jettyRun,并把 Debugger 连结到指定的 Port 上一次完成。所以每次重启调试只要一个按键,再切换回浏览器重新载入页面即可。

如果不是要调试,只是要看运行的结果,可以将【Run/Debug Configuration】的下拉清单切换成 Gradle 类型的项目后,按下运行图标的按钮即可。

在 GitHub 上有人提供了一个可以使程序更简易的 Plugin 叫 Gretty,有兴趣可以自行研究看看。接下来会进入到系列文章的最后一篇:测试

以下为完整的 build.gradle 的内容:

apply plugin: "war"
apply plugin: "jetty"

import org.gradle.api.plugins.jetty.internal.Monitor
[jettyRun, jettyRunWar]*.doLast {
    /**
     * THIS IS A WORKAROUND! THE CURRENT VERSION OF THIS TASK DOESN'T START A WATCHER IN DAEMON MODE
     *
     * If starting the monitor fails, it may be because the jetty task was updated to fix this issue
     * When that happens, we shouldn't need the custom task any more
     *
     * Copied From: AbstractJettyRunTask
     */
    if (getStopPort() != null && getStopPort() > 0 && getStopKey() != null) {
        Monitor monitor = new Monitor(getStopPort(), getStopKey(), server.getProxiedObject());
        monitor.start();
    }
}

[jettyRun, jettyRunWar].each { jetty ->
    jetty.dependsOn jettyStop
}

dependencies {
   compile project(":utils")
   compile "javax.servlet:servlet-api:2.5"
}

jettyRun.daemon = true
httpPort = 8080
stopPort = 9090
stopKey = "stopKey"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,355评论 6 343
  • 原文链接:http://www.dropwizard.io/1.2.0/docs/getting-started....
    Lance_Xu阅读 841评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 亲爱的,这个世界是有生命的 一、接受概念,对世界有觉知 笑来老师告诉我们,如果概念不存在,我们对概念的客观存就会毫...
    青青wh阅读 80评论 0 1