文章目录
官方参考:
https://developer.android.com/studio/debug?hl=zh-CN
调试应用
选择菜单 run -> attach debugger to android process
,注意不是Attach to process..
, Android Studio
的调试功能非常强大,同时支持多个调试上下文,可以attach
多个进程,每attach
一个进程,都会在AS底部的debug窗口打开一个新的Android debug
标签页。每个标签页都有独立的调试上下文,分别对应一个进程。AS可以自动在多个debugger
之间切换。
使用 Debug.waitForDebugger 调试
可以应用于以下场景:
- 被调试程序运行时会创建一个新进程,该进程很快执行完毕,来不及触发并attach
- 被调试程序运行时会启动一个新进程,但是想要调试触发动作之前代码逻辑
此时,可以使用Android提供的调试机制android.os.Debug.waitForDebugger();
Wait until a debugger attaches. As soon as the debugger attaches, this returns, so you will need to place a
breakpoint after the waitForDebugger() call if you want to start tracing immediately.
该函数会等待调试器attach。该函数在调试器attach后立刻返回,因此如果想开始调试,那么需要在`waitForDebugger`后设置断点。
attach debugger to android process
也对应快捷图标
有时Debugger
设置为Dual
会附加失败,这时可以切换到Java
试试
另外特别注意,比如VA的服务进程,在attachBaseContext
时附加断点,停留1-2秒,Service进程就会重启,用adb查看,你会发现它又创建了一个新的Service进程,从而原来的附加自动结束,显示:
Disconnected from the target VM, address: 'localhost:8602', transport: 'socket'
所以要选好适当的延后的时机设置断点。
求值表达式[Evaluate Expression]
要在当前断点对某个表达式求值,在断点处,右键选择Evaluate Expression
,之后你可以执行任何你感兴趣的表达式,并查看结果:
条件断点
例如:
for (Integer i=0; i<1000; i++) {
VLog.e("hgy413", i.toString());
}
我们想在i=500
时断下,在断点处鼠标右键就会弹出条件断点的小框,输入i.equals(500)
运行后结果如下:
日志断点
我们经常做的事情就是在代码里面添加日志信息,但是我们添加了日志代码需要重新编译, 从而浪费时间,这时就需要日志断点。
以前面示例继续,结合条件断点,我们想在i=300
时打印一条日志,我们可以这样设置:
右键弹出的是小框(如上面gif),去掉Suspend
勾选或点击More
,才会出现下图大框:
关键设置示图:
结果:
但在logcat
中其实会输出一连串hgy413 i=xx
日志,说明它不受条件控制
方法断点
很多时候我们关心的是某个函数的参数,返回值,我们可以在函数级别进行调试,最简单的是在你感兴趣的函数头那一行设置断点,这时候你会发现断点图标有点不一样:
而它断下后,你继续运行,它会在函数尾自动断下,如下图所示,它会自动在函数尾增加一个断点,用于你观察返回值。
异常断点
我们希望程序发现某种异常时断下,这时可以使用异常断点,Run -> View BreakPoints
,选择Java Exception Breakpoints
点击左上角的 ➕ ,会出现一个选择框;选择Exception Breakpoint
,然后会出现一个对话框,选择你感兴趣的异常:
数据断点(访问或修改)
Run -> View BreakPoints
,选择Java Field Breakpoints
, 我们可以在某个Field
被访问或者修改的时候让程序断下来
设置被访问或者修改的图如下:
设置变量的值
在调试过程中,你发现某个变量的值跟你预期的结果不一样,或者你怀疑某变量出现某些特殊值时程序会发生崩溃,在变量区右键你想监控的变量,设置你想要出现的值,然后继续运行。
源码调试
- 模拟器调试 + AndroidXRef 下载
- 真机调试,需要设置参数
debuggable
为1, 参考IDA 动态调试简介,同时刷原生Android系统。 - 要调试
android sdk
,必须保证编译版本和机器上的版本完全一致,简单的说,就是要保证是同一份代码, 不然断点就可能错位。 - 找对进程,如
frameworks\base\services\core\java\com\android\server
中的代码总是位于sytem_process
进程中,具体参考下面:
对应AOSP
的目录结构:
第一层:应用程序层(applications
)对应根目录下platform/packages/apps
第二层:应用程序框架层(application framework
)对应根目录下的platform/frameworks
第三层:运行库层包括运行库(libraries
)和android运行时环境(android runtime
)
运行库(libraries
)对应目录很多,其中libc
库对应的是platform/bionic
android运行时环境(android runtime
),Core Libraries
对应根目录下的platform/libcore
,Dalvik Virtual Machine
对应根目录下的platform/dalvik
,不过现在已经是ART
了,所以目录是platform/art
第四层:Linux内核层对应根目录下的kernel
,每一个目录对应了一个kernel
的版本
三、四层中间还有个硬件抽象层(HAL
)对应根目录下的platform/hardware
1. 调试framework中的代码
以API 25
为例,先下载源码:
你可以直接用android sdk 25
中的源码,也可以自己copy一份android sdk 25
中的源码到本工程,不需要编译,反正只需要保证是同一份代码,就行了
简单写一个demo,然后新建一个API 25
的模拟器,并把demo安装到模拟器。
ActivityThread.java
ActivityThread
的main
是app启动的入口,之后会走到
final IActivityManager mgr = ActivityManagerNative.getDefault();
mgr.attachApplication(mAppThread);
–>ActivityManagerNative.java
public void attachApplication(IApplicationThread app) throws RemoteException {
.....
mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0);
.....
}
这里使用了iBinder
跨进程通讯,会调到ActivityManagerService
的attachApplication
,而它位于system_process
进程
使用Android Studio附加到模拟器的system_process
进程
利用Android Studio搜索到attachApplication
,并下断点,注意,断点要设置在适当的位置,具体参考前面所述
从模拟器中启动demo, 理论上会断点成功:
2. 调试其他进程的代码
比如调试系统Setting,先在 AndroidXRef 中找到 API25
的代码,也就是 http://androidxref.com/7.1.1_r6/ , 系统App的源码在/packages
这个子目录下面, 搜索Settings
关键字就可以找到源码位于:
http://androidxref.com/7.1.1_r6/xref/packages/apps/Settings/ 然后我们把要调试的文件下载下来
androidxref
只支持下载单个文件,可以通过 https://android.googlesource.com/ 找到对应的版本下载整个目录,打开 https://android.googlesource.com/ 然后直接搜索packages/apps/Settings
,就可以找到了,然后选择对应的android-7.1.1_r6
分支
打开AndroidManifest.xml
可以查到包名为:com.android.settings
,我们先attach
到这个进程 :
查到设置程序的入口界面为:Settings
,它继承于SettingsActivity
,我们在SettingsActivity
的onCreate
里面打一个断点,然后点击启动Setting
程序,即可断下来了:
如果你觉得下载一个完整目录太麻烦,最简单的方式是在Android Studio
中新建一个同名的文件,如Settings.java
,从源码中复制一份相同的代码,这样就可以在你的同名文件中直接下断点了,注意包名要一致
,比如都是com.android.settings
3.行号不对应怎么办?
行号不对应带来的一个首要问题就是,下断点的时候都有可能出现问题;比如你在TestClass
的第100行下了一个断点,但是由于行号不对应,有可能真正执行的代码第100行是没有意义的空行或者是在下一个函数里面,这样断点就没有起到应有的作用了。
要解决行好对应的问题,必须使用方法断点
;我们直接在某个函数的入口设置断点,这样即使行号对不上,也能在正确的入口出断下来,这一点非常重要。
在Android Studio的调试器的左边,显示了每一个线程执行的栈桢,栈桢里面包含了当前线程丰富的信息:
看到没,真正运行的代码在哪一行,当前运行的是什么函数, 局部变量是多少,一目了然;接下来你在step into/out的时候,不能以源代码的行数为准,而应该以这个栈桢所显示的代码行数为准。
4.利用xposed调试system等系统进程的启动流程
首先我们需要一个装了xposed框架的模拟器:参考xposed在真机和AVD上安装流程
然后写一个简单的xposed插件,打印所有的进程我, 确认system_process
的进程名为android
, 这时就简单过滤下,加个 android.os.Debug.waitForDebugger();
再附加,如下简单代码:
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// system_process对应的名字为android
if (!TextUtils.isEmpty(lpparam.processName)
&& lpparam.processName.equals("android")) {
android.os.Debug.waitForDebugger();
从AMS初始化和启动简介可以看出,系统启动AMS的流程是从startBootstrapServices
开始的,如下调试断下了:
之后进入AMS的构造函数:
5.调试源码版本不对应
这种问题经常出现,比如目标手机的SDK是23,我在android-23
中的源码下了断点,却发现它跳转到android-26
的源码中了,这是因为系统判断android studio
的compileSdkVersion
为26
, 很明显,这严重影响内部调试, 这是已知issues: https://issuetracker.google.com/issues/37058409
解决方式:
- 修改
compileSdkVersion
,但这可能会引起大量的错误 - 使用issues中介绍的临时解决方案,把原始的
android-26
文件夹备份,并把android-23
文件夹复制一份,改名为android-26
,记得调试完再改回去!