前言
安卓开发的过程中,需要对开发的程序进行调试。谷歌官方和非官方,提供了很多帮助我们调试代码的工具和方法。有的使用起来很简单,有的则功能强大,很少有机会接触。因此,我们打算由浅入深的向同学们介绍,知道针对不同的场景,使用合适的工具。
本文针对的读者是:
- 对安卓程序调试需要指导的新手;
- 对程序调试没有太多经验的开发者;
在开始以前,假设各位已经做好了如下准备:
- 已经在搭建好了安卓软件开发平台
- 一部安卓系统设备(手机或平板电脑);
- 一根连接电脑和安卓设备的数据线(通常是micro usb数据线);
- 半天时间。
- 耐心与求知欲。
本文将介绍到:
- 断点调试;
- 输出log调试;
- Android Device Monitor初步使用
- ADB工具
- wifi连接设备调试;
第1节 动态调试与静态调试
安卓系统上调试程序,主要通过两种形式:
1. 静态调试:程序在运行到某一个状态的时候,让它暂停,用工具查看程序此时的运行信息,比如某个变量的数值;查看完成后,让程序继续运行,恢复到正常的工作。
2. 动态调试:在程序中,添加日志信息(log),在程序运行的时候,将log指定的信息输出到调试的电脑上。整个过程不会打断程序的运行。
不管哪种调试方式,都需要手机与调试的电脑通过数据线相连,借此传递调试信息。
1.1 设备与电脑的连接
要在设备上进行调试,首先要打开设备的开发者选项,不同品牌的安卓设备界面虽然不尽相同,但使用方式都大同小异:
- 启动安卓设备上的“设置”应用,进入“关于手机”选项卡;
- 连续点击“版本号”,直到出现
您现在处于开发者模式!
的提示信息; - 返回上级菜单,进入“开发者选项”,
开启
调试模式,钩上USB调试
;
将手机和电脑用USB数据线连接起来。
在Windows系统,需要为连接上的设备安装ADB驱动:
- 在安豆网的资源下载下载ADB的Windows驱动到电脑本地;
- 在“我的电脑”上点鼠标右键,选择“管理”,打开“设备管理器”,可以看到没有安装驱动的设备;
- 为它更新驱动,选择“浏览计算机查找”,
- 指定下载的ADB驱动目录位置,点击确定后,驱动很快就安装成功了。
- 点击Android Studio的
Android Monitor
窗口,就能看到这个连接上的设备了,这个窗口还输出了手机端打印的运行信息。
1.2 部署应用
将应用程序通过Android Studio运行到设备上有两个方式:run app
和debug app
。
debug app
可以设置断点,进行代码的静态调试;而run app
不能设置断点,不能对代码进行静态调试。这两种方式可以通过菜单项启动,也可以通过快捷键开始。
- 点击菜单栏中的绿色的小三角,就是
run app
;或者使用
debug app
的快捷按键shift+F10
; - 在选定的设备上双击,
此时就可以在设备上看到,我们的程序运行起来了。
1.3 静态调试方法
静态调试就是冻结应用运行的状态,仿佛时间停止了一般,然后我们逐一观察此时程序的各个参数是否符合我们的预期。这种调试方法适用于对时间不敏感的程序。也就是说被调试的程序线程不需要依赖别的线程,即使暂时停止工作也不会影响别的工作线程或者受别的工作线程影响。
- 在希望代码暂停运行的地方打断点——在代码前点击一下,出现一个红色的圆点,如果想取消,再点击一次即可。
- 用
debug run
的方式部署程序。当程序运行到这段代码的这个位置时,程序将停止下来,切换到Debug
窗口。这时,我们就可以观察各个参数了。例如下图右半区域就列出了停止时,各个变量的值;左边区域展示了当时函数到调用栈(谁调用的这个函数)情况。我们可以逐一分析,详细观察,看这些值是否符合我们的预期。 - 下面的功能,将指定程序暂停后,执行下一步的走向。这些都是断点调试经常使用到的、由我们控制程序运行步骤的功能,所以尽量记住它们对应的快捷方式;
Step Over
:执行完成当前断点停留处的代码,然后停在下一行待执行的代码处。void fun1() { int a = 0; fun2(); //正等待执行的代码 a++; //下一步待执行的代码 } void fun2() { int b = 0; b++; }
Step Into
:如果当前断点执行处是一个函数,那么执行Step Into
后,进入到该函数。void fun1() { int a = 0; fun2(); //正等待执行的代码 a++; } void fun2() { int b = 0; //下一步待执行的代码 b++; }
Step Out
:在当前断点执行处执行Step Out
后,返回到调用该函数的地方等待执行。void fun1() { int a = 0; fun2(); //下一步待执行的代码 a++; } void fun2() { int b = 0; //正等待执行的代码 b++; }
Resume Program
:程序继续往下执行,直到遇到下一个断点。
有的调试功能带有force
,例如force step over
,它们可以在这种场景下使用:当你想进入不是你写的源代码查看调用过程,但是使用不带force
的功能,却没有起作用。简单来说就是不带force
的用来跟踪自己写的代码,带force
的用来跟踪SDK里的源码。
1.4 动态调试方法
对于那些和时间相关的程序(不能让程序暂停,等你慢慢观察),我们就不能使用静态调试方法了,得采用动态调试,添加log的方式。
Log的中文名字叫做日志,在编程界表示程序运行过程中打印出的信息。根据log我们就知道现在程序运行到什么地方了,log还可以携带程序中某些变量的信息输出,让我们更精准的知道程序当前运行的状态。
1.4.1 代码中添加log
在代码中添加一段函数,就能通过特别的工具输出这些log。
在Android代码中添加log的方式如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("TAG", "debug info: function=" + "onCreate()");
}
这里面使用了Android提供的Log库。
1.4.2 log的查看
添加了log信息后,将程序通过debug app
部署到设备上,就能在Android Monitor
工具的logcat
窗口中看到对应的信息了。
输出的调试信息如下:
03-22 03:22:39.778 8357-8357/com.anddle.calculator D/TAG: debug info: function=onCreate()
03-22 03:22:39.778
:log产生的时间;
8357-8357
:设备上运行这段代码的进程ID(PID)和线程ID(TID);
com.anddle.calculator
:设备上运行这段代码的包名;
D
:这条log的类型。我们输出这条log使用的是Log.d()
,所以就显示D
;如果使用Log.i()
就显示I
,以此类推;
TAG
:就是Log.d()
函数的第一个参数;
debug info: function=onCreate()
:就是Log.d()
函数的第二个参数;
Android应用开发的Log库提供了几种不同等级的log:Verbose
Debug
Info
Warining
Error
,我们可以根据自己log的需要加不同等级的log,使用的形式为:
Log.v(“TAG”,”content is verbose”);
Log.d(“TAG”,”content is debug”);
Log.i(“TAG”,”content is info”);
Log.w(“TAG”,”content is waring”);
Log.e(“TAG”,”content is error”);
我们在应用中调试程序,通常使用d。
1.4.3 log的添加规则
在应用的开发当中,我们对log的添加有一些不成文的技巧,可以提高程序的开发效率。
- 在调试应用的同一个功能时,为它定义同一个
TAG
。例如,一个应用有网络通信和本地文件读取两个大功能。那我们就可以为有网络通信定义一个TAG叫做Network
,为另一个本地文件读取定义一个TAG叫做FileAccess
。这样当我们查看对应的log信息时,就很容易通过关键字把它们从log的海洋里区分开。将TAG
定义成一个字符串常量,便于对log输出信息的修改。static final String TAG1 = "Network"; static final String TAG2 = "FileAccess"; ...... Log.d(TAG1,"这是网络通信相关的log!"); Log.d(TAG2,"这是文件读取的log!");
- 使用
Debug开关
控制log信息是否输出。在调试应用的时候要添加很多log,当应用发布的时候,又要去掉这些log信息。添加或者删除这些log,会增加很多工作。所以我们需要使用Debug开关
。static final boolean DEBUG = true; //true表示打开开关,false表示关闭开关; ...... if(DEBUG) { Log.d(TAG,"Debug开关打开才输出!"); }
- 输出函数的调用栈。有的时候,我们不仅关注程序执行到当前时各个变量的值是什么,还关心这个函数是怎么被调用到的。那么我们可以在代码中,添加输出调用栈的信息:
Log.d(TAG, Log.getStackTraceString(new Throwable()));
Log.d()
在调试代码时使用,用Debug开关
控制;Log.e()
在出现意外而重要的错误情况时使用,不用Debug开关
控制;Log.v()
Log.i()
和Log.w()
在需要输出运行状态并且不涉及暴露应用实现信息的情况时使用,不必用Debug开关
控制;当然,开发者想怎么用这些log类型就可以怎么用,并没有特别的约束。