图片来自:101.dev/
本文作者: 雪谷

前言

调试功用做为开发的必备神技,熟练把握后能极大的进步开发功率,再也不必为频频运转代码而苦恼了。文章一起还会详细介绍调试的原理以及一些调试过程中的常见问题,想知道为什么办法断点那么慢?

接下来将从以下四个方面来讲解调试是怎样运作的:

  1. 调试操作
  2. 调试实战
  3. 调试原理
  4. 常见问题

调试简介

这儿咱们介绍一下调试的常见操作,灵敏把握这些操作,能够协助咱们快速定位到对应代码或许获取想要的信息。

运转调试

敞开 Debug 调试形式有两种办法:

Android 调试实战与原理详解

Debug Run:直接以 Debug 形式运转 APP,该形式的长处是能够调试程序启动相关的代码, 例如 Application.onCreate()

Attach To Process:在程序运转中挑选进程来调试,该形式的长处是随时可敞开、封闭 Debug 形式,运用灵敏方便。

留意:Debug Run 会导致程序全体变慢,主张运用等待调试,运用该办法能够在启动运用后处于等待状态,在敞开调试后,运用才会走初始化流程,有两种办法敞开等待断点:
办法1:「开发者选项 – 挑选调试运用」的办法来调试运用启动阶段代码。详细办法为「挑选调试运用」-> 「运转运用」-> 「Attach To Process」,然后等待断点履行即可。
办法2:运用adb指令adb shell am set-debug-app -w --persistent 包名敞开,「-w」即表明运用启动时等待调试程序;封闭运用adb shell am clear-debug-app

调试操作

下面介绍一下 Debug 过程中的常见操作:

Android 调试实战与原理详解

  1. Show Execution Point:跳到当时履行的断点处。
  2. Step Over:单步履行,履行到当时行的下一行。
  3. Step Into:进入正在履行的办法。
  4. Focus Step Into:同3,可是能够进入源码,在3无法进入的状况下,能够尝试该操作。
  5. Step Out:跳出正在履行的办法。
  6. Drop Frame:返回到当时办法的调用处。
  7. Run to Cursor:运转到光标处(光标有必要在当时断点方位后)。
  8. Evaluate expression:核算选中的变量的值。

断点类型

Android 调试实战与原理详解

断点分为以下四种类型:
行断点: 当履行到此行是停止履行,等待调试。
特点断点:打在类的成员变量上,当变量初始化或变量的值改变时触发断点。
反常断点:当抛出指定反常时触发断点。
办法断点:当需求知道一个办法的调用方时。

这儿着重讲一下办法断点的运用场景:
如下所示,有个接口 IMethodTest,一起有两个类 MethodTestImpl1MethodTestImpl2 完成了该接口,在 IMethodTestprintMethod() 上打上办法断点。

Android 调试实战与原理详解

在代码中实例化了 MethodTestImpl2 来调用 printMethod()

Android 调试实战与原理详解

最终当 Debug 到该办法断点时,会自动走到 MethodTestImpl2printMethod() 的完成中。

Android 调试实战与原理详解

注:办法断点只支撑 Java 代码。

调试实战

咱们都知道调试是进步开发功率的利器,那么它是怎样协助开发者的呢?
答案便是「检查信息」和「削减编译次数」。

检查信息

当程序运转成果并不如你的预期时,经过调试来检查当时内存里的变量以及仓库信息,是最快速定位问题的办法。

检查局部变量的办法如下图所示

Android 调试实战与原理详解

体系自动打印:在当时调试方位之前的代码右侧会自动打印当时栈帧里保存的变量值。
鼠标悬停:鼠标悬停在一个变量上几秒后,会列出该变量的详细信息。
Variables 区:在 Variables 区里会自动打印当时办法里的变量详细信息。

检查全局变量有两种办法

Android 调试实战与原理详解

在 Variables 区增加监听:点击左侧操作栏里的「+」,输入对应变量值,即可实时调查该值的变化。

Android 调试实战与原理详解

在 Evaluate Expression 中输入想要调查的变量,回车后即可检查当时时刻该变量的值。

注:检查局部变量和全局变量需求断点方位能访问到该值。

检查仓库信息

在调试页面的「Debugger」Tab下能够检查当时的调用仓库。

Android 调试实战与原理详解

需求留意的是,一个线程只会被一个断点堵塞,可是不同线程是能够一起堵塞的,能够切换下拉框来切换线程,赤色圆点表明正在被堵塞的线程

Android 调试实战与原理详解

削减编译次数

越大的项目运转起来越是缓慢,而有时咱们只是修正了一行代码乃至是一个字符,这时再去从头编译是功率十分低下的,而灵敏运用各种调试技巧,就能够协助咱们在不从头运转项目的前提下,去修正运转中代码。

Android 调试实战与原理详解

运转期代码植入

想修正现已运转起来的代码,有两种办法:

在 Variables 区中运用 setValue。

Android 调试实战与原理详解

运用 Evaluate Expression。

Android 调试实战与原理详解

Evaluate Expression 是一个十分强壮的功用,能够打开履行恣意的代码段。灵敏运用能够很多的削减编译次数,例如:

  • 修正网络恳求、外部跳转等来历的数据,模拟各种场景。
  • 履行某些代码,直接检查成果。
  • 履行某一段反常代码,直接检查报错信息。

日志断点

日志是辅助开发排查问题的常见手段,可是在代码中增加日志存在一些不方便的状况,例如:

  • 需求从头运转程序。
  • 开发完成之后需求去除对应的日志代码。
    而运用日志断点就能够防止以上问题,运用办法为在断点方位右键,取消 Suspend 框的勾选,一起勾选 Evaluate and Log 并输入想要的内容。

Android 调试实战与原理详解

条件断点

当一个断点会被多次履行,而调试时只需求在某些特定条件下才挂起,能够运用条件断点。运用办法为在断点方位右键,在 Condition 框中输入条件表达式,回车,这时断点右下角出现一个「?」即为条件断点成功挂载。
留意,条件断点的表达式返回值有必要为 true 或许 false,否则断点报错。

Android 调试实战与原理详解

反常断点

当开发者知道接下来必定会报某一个反常,可是又不知道会是哪段代码触发时,能够尝试运用反常断点。运用办法为在断点办理界面点击「+」,增加 Java Exception Breakpoints。

Android 调试实战与原理详解

然后输入你想要捕获的反常,留意,这儿也会捕获体系抛出的反常,捕获时请仔细调查。

Android 调试实战与原理详解

多线程断点

多线程是日常开发中常见的问题,针对一系列线程切换场景,调试工具也有对应的办法来辅助咱们定位问题。

这儿请先思考一下这个示例,在不敞开断点的状况下,下图的代码履行后会输出什么信息?

Android 调试实战与原理详解

答案便是「无法确认」。
没错,在 CPU 的时刻片履行机制下,假如不加以操控,开发者是无法预估线程履行次序的。而直接写一系列的线程操控代码耗时不小,有没有办法能先让线程依照开发者想要的次序去履行呢?请持续往下看:

在断点方位上右键,出来的办理界面里有 All 和 Thread 两个选项:

  • All 表明堵塞一切线程,即一切线程都走到当时断点方位后,才能持续往下走。
  • Thread 表明堵塞当时线程,即当时线程的代码走完后,才会走其他线程。

Android 调试实战与原理详解

所以结合上面的示例:
All 选项的输出成果为:一切线程先履行完 start,再履行 end,可是哪个线程先履行无法确认。

Android 调试实战与原理详解

Thread 选项的输出成果为:一个线程先履行完 start,再履行 end,然后是另外一个线程,可是哪个线程先履行无法确认。

Android 调试实战与原理详解

调试Release包

调试Relase包偏Android逆向,因为篇幅有限,这儿首要介绍和调试相关内容,前期预备能够看这儿DebugApkSmali。

在反编译 APK,Smali 文件生成后,咱们需求把手机和 Android Studio 相关上,这儿需求运用 Remote 功用,详细流程如下:

挑选 Edit Configurations。

Android 调试实战与原理详解

新增 Remote JVM Debug,Name 随意,Port 不与现有端口抵触即可。

Android 调试实战与原理详解

检查需求调试的页面坐落哪个进程,先经过adb shell dumpsys activity top | grep ACTIVITY检查栈顶页面(这儿调试的是知乎),然后在 AndroidManifest.xml 中检查对应 Activity 的 android:process(没有该特点的话就看 application 的 process)。

Android 调试实战与原理详解

经过adb shell ps | grep com.zhihu.android检查该进程对应的 PID,依据下图能够得到对应的 PID 为16282。

Android 调试实战与原理详解

最终经过adb forward tcp:5005 jdwp:16282衔接上手机和 Android Studio,就能够开始愉快的调试。

经过上面的介绍,咱们了解了调试 Release 包的办法,可是咱们有没有一种雨里雾里的感觉呢,为什么知道了端口就能够相关上?tcp 和 jdwp 又是什么意思?他们之前又是怎样传输数据的呢?带着这些疑问,咱们一起来看下调试原理。

调试原理

假如用简略的一句话来解说调试原理,能够概括为「经过ADB协议以及JDWP协议来完成调试器与虚拟机之间的通讯」,如下图所示,调试的过程,其实便是通讯的过程,了解了怎样通讯以及传递了那些信息,就明白了调试的中心原理。后续内容请都参考该图来了解。

Android 调试实战与原理详解

ADB 架构

首先需求了解的是 ADB 架构,其间包括了三个部分:ADB Server、ADB Client 以及 ADB Dameon。

ADB Server

运转在电脑上的进程名为 adb 的后台进程,端口号5037,作用是办理 ADB Client 与 ADB Dameon 进程的通讯。如下图所示,经过 adb device (恣意 adb 指令均可)指令能够从常驻的后台进程 adb 上 fork 一个子进程用于当时的通讯。经过指令检查相关进程能够发现会有三个:

  • Android Studio 进程衔接 adb 进程的通讯。
  • adb 进程衔接 Android Studio 进程的通讯。
  • adb 常驻进程。

Android 调试实战与原理详解

ADB Server 中包括 Local Service 和 Remote Service,Local Service 用于与 ADB Client 交互,Remote Service 用于与 ADB Dameon 交互。

ADB Client

ADB Client 运转在电脑上,一般经过指令行或许 Android Studio 履行 adb 指令来与其交互。ADB Client 的首要职责是解析指令,做预处理,然后发送给 ADB Server,这儿分为两种状况:

  • ADB Server 能处理的指令就自己处理,如 adb version。
  • ADB Server 不能处理的指令就发送给 ADB Dameon,并接受返回音讯,如 adb devices。

ADB Dameon

ADB Dameon 运转在手机上的服务进程,进程名为 adbd,在手机启动后,由 Zygote 进程创立。ADB Dameon 的首要职责是:

  • 为手机供给adb服务。
  • 创立 Local Service 和 Remote Service,Local Service 用于与 JVM 交互,Remote Service 用于与 ADB Server 交互。

了解了三者的分工后,能够经过下图对 ADB 架构有一个较为全体的了解。

Android 调试实战与原理详解

看到这儿,咱们应该就能了解为什么衔接手机和 Android Studio 的指令是adb forward tcp:5005 jdwp:16282了,它实际上便是把 ADB 和 手机虚拟机进行衔接,一起也能够发现 ADB Server 和 ADB Dameon 之间的协议既能够是 USB(数据线)也能够是 TCP 的办法,其间 TCP 便是调试功用支撑 WIFI、长途的基础。

注:因为篇幅有限,这儿只对 ADB 架构做了简略的介绍,感兴趣的同学能够自行学习。

JDWP协议

在了解了 ADB 协议后,咱们知道了指令是怎样从 Android Studio 或许指令行传输到手机上的 ADB Dameon 的,那么 ADB Dameon 又是怎样与虚拟机交互的,以及传输协议中的数据格局又是怎样的呢,这儿就需求了解 JDWP 协议了。

概念介绍

JDWP 是 Java Debug Wire Protocol 的缩写,其本质上是调试器和方针虚拟机进行调试交互的通讯协议,经过指令包和回复包两种格局来传输数据。
这儿有四个概念需求了解:

  • 调试器(Debugger):Android Studio、Eclipse、DDMS、Terminal 等,他们都完成了支撑 JDWP 通讯接口。
  • 方针虚拟机(Target VM):JVM、Art、Dalvik 等,在虚拟机启动时,会加载JDWP模块。
  • 指令包(Command packet):调试器发送给虚拟机用于获取程序状态信息或操控程序运转,或许虚拟机发送给调试器用于告诉事情触发音讯。
  • 回复包(Reply packet):虚拟机发送给调试器用于回复指令包的恳求或许履行成果。

它们之间的交互如下图:

Android 调试实战与原理详解

数据包

JDWP 数据包包括包头和数据两部分,数据部分便是简略的二进制数据流,咱们这儿注重讲一下包头部分的结构,这也是调试指令传输的中心。

Android 调试实战与原理详解

如上图所示,指令包和回复包的前三部分结构是相同的:

  • length:4字节,数据包长度,包括包头和数据。
  • id:4字节,数据包序号,指令包和回复包有必要保持一致。
  • flags:1字节,数据包类型,0x80 表明指令包,0x00 表明回复包。

不同之处在于最终2字节:

  • 指令包包括 cmd set(指令分组)和 cmd id(指令序号)两部分,别离占1字节。
  • 回复包里存放的是 error code 错误码,非0即为存在错误,占2字节。

常见的指令分组和序号依照功用大致分为18组指令,包括了虚拟机信息、类、目标、线程、办法、事情等不同类型的操作指令。见下图:

Android 调试实战与原理详解

该图片来历FreeBuf。

检查完整指令组及详细信息见:指令组。

这儿以获取虚拟机版别的指令 VirtualMachine:version 为例演示,协助咱们了解指令到底是怎样传输的。
首先来看获取虚拟机版别会回复哪些信息:

Android 调试实战与原理详解

经过上述表格能够推导出指令包与回复包的包信息为:

Android 调试实战与原理详解

把对应编码转换成字符串为:

Android 调试实战与原理详解

需求留意,非根本数据类型的内存结构,例如 String,运用「长度」+「字符数据」的形式。以 vmName 字段为例,DalvikVM 的 ASCII 码为「44 61 6c 76 69 6b 56 4d」,DalvikVM 的长度为8,所以归纳后 DalvikVM 的返回数据为「00 00 00 08 44 61 6c 76 69 6b 56 4d」。而 jdwpMajor 为纯数字,所以 jdwpMajor 的返回数据为「00 00 00 01」。

到这儿调试原理就讲完了,原理部分只是从全体架构的层面为咱们介绍了一下,内部还有很多的知识点值得咱们去深究,感兴趣的同学能够自行学习。

常见问题

在讲完了调试实战和原理之后,咱们来看一些常见的调试问题:

  • 断点自动断开
    现象:在某些机型上,例如华为非鸿蒙体系、部分 OPPO、一加设备等,当断点在 Activity、Fragment 的生命周期办法上超过10秒或许卡住页面展现超过必定时刻(不同设备时长不一致)时,会出现断点自动断开的状况。
    解决办法:运用非堵塞式的日志断点。

  • 无法Attach to Process
    现象:在挂载进程进行调试时,出现 Error running 'Android Debugger (-1)': Invalid argument : Argument invalid [port] 的报错,这时是因为 adb 进程端口号被其他进程抢占了。
    解决办法:运用 adb kill-server 杀死 adb 进程,然后运用恣意一个 adb 指令(adb devices)fork 一个新的 adb 进程即可。

  • 办法断点导致Debug卡顿
    现象:在运用办法断点时,调试器会变得反常卡顿,这是因为办法断点需求盯梢办法的入栈和出栈,每次进出都要发送指令给调试,详细流程如下:
    1.把办法断点参加断点列表。
    2.调试器发送指令告诉虚拟机需求监听 Method Entry 和 Method Exit。
    3.虚拟机每次收到 Method Entry 或许 Method Exit 后发送事情给调试器。
    4.调试器判别是否在断点列表中。
    5.存在则向虚拟机发送 SetBreakPoint 恳求挂起,否则发送恳求释放该办法栈。
    解决办法:
    1.依据实际状况放开 Method Entry 或许 Method Exit,如下图所示。
    2.用完即弃,及时去除办法断点。
    3.不要用!运用行断点(官方主张)。

Android 调试实战与原理详解

总结

调试是一个优异开发者必备的技巧,对提高开发功率有极大的协助。把握调试原理也能够协助开发者更好的了解 Android 架构,是一个高档开发者的必经之路。

参考资料

  • Android 调试桥 (adb)
  • jdwp_handler
  • JDWP指令行调试
  • Android长途调试的探索与完成
  • Java Debug Wire Protocol Specification Details

本文发布自网易云音乐技术团队,文章未经授权制止任何形式的转载。咱们常年招收各类技术岗位,假如你预备换作业,又恰好喜欢云音乐,那就参加咱们 grp.music-fe(at)corp.netease.com!