Android调试优化篇
为了开发出商业级的应用程序,大规模的測试是不可避免的,同一时候为了提高应用程序的执行速度,须要进行必要的优化。在Android中。提供了丰富的调试与优化工具供开发者应用,主要包含模拟器和目标端等两种场景下使用的工具。
1.Android调试
软件调试是一个伴随软件开发的必定过程。好的调试环境和工具能够提高开发的效率。在Android中,除了提供GDB调试外。还提供了DNSS、Logcat、Dmtracedump、DevTools、Procrank、Dumpsys等开发工具供开发人员使用,当中DMSS包括了多个组件。
当发生Android ANR错误时,错误信息会保存在\data\anr\traces.txt中,这有助于在无法实时查看日志的情况下分析Bug。
(1)Logcat日志调试
android.util.Log经常使用的方法有下面5个:Log.v()、Log.d()、Log.i()、Log.w()及Log.e()。其分别相应VERBOSE、DEBUG、INFO、WARN、ERROR等不同等级。
开发人员能够依据场景的不同选择不同的方法。普通情况下,对于纯粹的调试信息。笔者建议採用Log.d()方法。
须要说明的是。android.util.Log仅适用于应用层,对于框架层的调试。须要使用android.util.slog类。
C/C++依旧支持log的输出。
(2)dmtracedump跟踪
dmtracedump是一个基于图形界面的使用方法间调用关系的工具。在使用dmtracedump前,必须安装Graphviz,在Linux下安装Graphviz的方法例如以下:
#apt-get install graphviz
dmtracedump的使用方法例如以下:
dmtracedump [-ho] [-s sortable] [-d trace-base-name] [-g outfile] <trace-base-name>
在实际操作中,为了分析函数间的调用关系。首先要在须要分析的方法中设置跟踪的起始点和结束点,方法例如以下:
開始跟踪:Debug.startMethodTracing("loadEvents"); //输出文件为loadEvents.trace
结束跟踪:Debug.stopMethodTracing();
程序执行结束后,就可以在\sdcard下看到loadEvents.trace就可以分析时间关系和方法调用关系。当然这是文本界面的,要通过图形界面来显示就要用到dmtracedump。方法例如以下:
#dmtracedump -g out.png clac.trace
浏览out.png就可以看到相关函数调用关系图,其结点的格式例如以下:
<ref> callname (<inc-ma>,<exc-ms>,<numcalls>)
上述格式中,ref表示编号,callname表示方法名。inc-ms表示调用事件,exc-ms表示运行时间。numcalls表示运行次数。
(3)Dev Tools调试
Dev Tools是Android特有的、帮组在物理设备上开发调试应用的工具。默认在SDK中存在。
通过Dev Tools能够打开一些设备帮组调试的设置,如USB的设备等。还能够查看安装包、启动终端等,这些对开发人员在物理设备上调试应用显得十分实用。
(4)屏幕截图分析
还有一个简单却十分实用的工具是DDMS下的screen capture工具。通过它,开发人员能够直接截图,供自己或同事进行对应的分析,这在须要沟通的场景中十分实用。
screen caption工具支持界面的手工刷新和旋转,其保存图片的格式为PNG。
眼下screen capture工具对视频播放尚无法提供有效的支持。这可能是因为视频帧速率过快,而screen capture工具尚无法支持这么高的帧速率导致的。
(5)内存调试
内存调试不仅限于C、C++等原生代码。Java也相同须要,在Android中。有多个工具如DDMS、Procrank、Dumpsys等能够获取系统执行期的内存信息,这些信息十分有助于进行内存调试。
1)DDMS内存调试
在Eclipse中集成的DMSS并没有呈现出所有的DMSS能力,假设希望观察系统更具体的信息。能够直接启动DK\tools\ddms。
2)Dumpsys内存调试
通过Dumpsys能够进行非常多分析。其分析内存的方法为adb shell dumpsys meminfo。
3)Procrank内存调试
另外,通过adb shell procrank也能够查看到进程占用内存的情况,当中Uss(Unique Set Size)的大小代表属于本进程正在使用的内存大小。这些内存在该进程被撤销后。会被全然回收,Uss也是进行内存泄露观察时的重点:Vss(Virtual Set Size)和Rss(Rsdident Set Size)表示共享库的内存使用,可是因为共享库的资源一般占用比較大,因此会使进程自身创建引起的内存波动所占比例减小。而Pss(Proportional Set Size)则依照比例进行共享内存切割。
通过间隔性地执行Procrank来观察进程占用Uss内存的变化。能够分析应用是否存在内存泄露。Procrank的代码位于system\extras\procrank目录。Procrank的使用方法为:
procrank [-W] [-v | -r| -p| -u| -h]
考虑到Android是基于Dalvik虚拟机的,垃圾回收并不是实时的。故通过单个界面的单次启动、关闭是无法确定内存是否泄露的。
一个号的策略是反复运行某个界面的启动、关闭。假设发现应用占用的内存不断上升,则能够推断该界面存在内存泄露。
4)Eclipse插件内存调试
在Eclipse中,有一个插件MAT(Memory Analyzer Tool)能够帮组分析Java层的内存泄露,其下载地址为http://www.eclipse.org/mat/。
MAT分析的是hprof文件,该文件里存放了进程的内存快照。
以下是从终端获取hprof文件的方法:
#adb shell
#ps //查看进程号
#chmod 777 /data/misc
#kill -10 PID //PID即进程号
这样就可以在\data\misc文件夹下生成一个带当前时间的hprof文件,但这个文件并不能直接被MAT读取,开发人员需借助hprof-conv将hprof转换为MAT能够读取的格式,然后才可用MAT进行分析。
hprof-conv的使用方法例如以下:
hprof-conv <infile><outfile>
2.Android布局优化
为了实现精细的布局。Android提供了两个Android布局优化,即Layoutopt和Hierarchyviewer。当中Layoutopt能够优化布局,帮组开发人员降低冗余信息,而Hierarchyviewer则可直接调试用户界面。
(1)Layoutopy优化
Layoutipt是一个优化布局的工具。它能够帮组开发人员分析採用的布局是否合理。并给出改动意见,其使用方法是:
layoutopt <directories/files to analyze>
详细方法例如以下:
layoutopt res/layout-land
layoutopt res/layout/main.xml
Layoutopt能够指出有问题的代码所在的位置和出问题的解决办法,还能够给出优化的建议。
(2)Hierarchyviewer优化
Hierarchyviewer同意开发人员调试和优化用户界面。
通常Hierarchyviewer开发人员能够清晰地看到当前设备的UI界面的实际布局和控件属性,这在复杂界面的调试中显得很实用。能够帮助开发人员高速定位问题。当然出于安全性考虑,Hierachyviewer仅能优化debug模式的应用。
发起Hierarchyviewer优化的过程例如以下:
1)启动物理设备或模拟器。
2)执行应用至欲优化的界面。
3)在终端启动Hierarchyviewer(位于SDK的tools文件夹下)。
4)选择设备和Activity。
Hierarchyviewer提供了两个层次的分析界面,即视图界面和像素界面。在Herarchyviewer的视图界面中,开发人员能够清晰地看到布局文件的层次关系。针对特定的UI控件,开发人员能够清晰地看到控件在屏幕上的位置以及各属性的值。
熟练使用Hierarchyviewer,能够加快UI调试效率。
3.Android測试
Android提供的几种測试工具能够帮组开发人员最大限度地提升开发质量和应用程序的兼容性。
这些測试工具中最重要的两个为Monkey压力測试工具和CTS兼容性測试工具。
(1)Monkey压力測试
Monkey工具能够模拟各种按键、触屏、轨迹球、导航、Activity等事件。此工具使用方法例如以下:
adb shell monkey [option] <event-count> //特定事件
adb shell monkey -p your.packege.name -v 50000 //50000随机事件
为了使开发的应用程序具有一定的稳定度,在使用前建议进行Monkey压力測试。须要注意的是。Monkey压力測试仅能模拟系统事件监測应用中存在的语法Bug,对于深层次的语义Bug依旧无能为力,并且对于网络功能。Monkey压力測试也无法进行有效的測试。
因为Monkey是通过载入特定Activity(category属性需为android.intent.category.LAUNCHER或android.intent.category.MONKEY)作为程序入口来进行測试的,故在进行Monkey压力測试时,easy形成孤岛的Activity,为了进行全面測试,须要为其添加Intent过滤器。对应的參考实现例如以下:
<activity android:name="Hello">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.MONKEY"/>
</intent-filter>
</activity>
须要注意的是。假设仅仅针对单个应用进行压力測试,Monkey会阻止对其它包的调用,当然针对单个应用的压力測试无法检測应用间交互可能存在Bug。为了測试系统内应用交互与冲突带来的问题,能够针对系统进行Monkey压力測试,方法例如以下:
#adb shell monkey -p -v 50000
在进行Monkey压力測试的过程中。Monkey会由于应用奔溃、网络超时及一些无法处理的异常而停止測试。建议在对网络应用进行压力測试时,忽略网络超时的情况,方法例如以下:
#adb shell monkey -p your.package.name -v 50000 --ignore-timeouts
假设希望忽略应用奔溃的情况,那么可运行例如以下方法:
#adb shell monkey -p your.package.name -v 50000 --ignore-crashes
在进行压力測试时。假设发生异常情况。如系统奔溃。那么Android会自己主动打印出相关的系统信息(如内存分配等)供开发人员分析。
另外,为了更好地进行Monkey压力測试,须要使外围设备的配置保持和实际产品同样,如默认的键盘时QWERTY键盘(这对于没有物理键盘的产品不适用)。
(2)JUnit回归測试
JUnit回归測试即白盒測试,通经常使用在单元測试场景中,如对非图形界面的接口的測试,这在开发框架性代码时很实用。
Android仅支持JUnit3,尚不支持JUnit4。
在Android中。Google对JUnit进行了封装,Android JUnit还支持图形界面如Activity、View等的測试。甚至支持对图形界面接口的功能压力測试。
Android JUnit的内容分主要分布在android.text中,另外android.text.mock和android.text.suitebuilder也提供了一些辅助和支持。
依据约束的不同。測试能够分为SmallTest、MediumTest、LargeTest、AndroidOnly、SideEffect、UiThreadTest、BrokenTest、SmokeSuppress等。当中AndroidOnly、表示測试项仅适用于Android。SideEffect表示測试项具有副作用;UiThreadTest表示測试项在UI主线程中执行;BrokenTest表示測试项须要修复。Smoke表示測试项为冒烟測试;Suppress表示该測试项不应出如今測试用例中。
为了进行JUnit回归測试,须要在Eclipse中创建Android Test Project,可通过运行File-New-Other-Android-Android Test Project命令完毕,运行測试的方法为右击project项,在弹出的菜单中运行run as-android JUnit Test命令。对于具有多个Instrumentation-TestRunner的測试project。在运行測试时。应该先在project的Run Configurations中指明InstrumentationTestRunner。測试完毕后,会自己主动给出測试结论。
在物理设备上借助Dev Tools也能够做JUnit測试,当然,通过am命令也能够运行JUnit測试。只是这样的方法比較繁琐。
1)JUnit測试的框架
Junit測试主要包含TestCase、Instrumentation等。
为了执行測试用例。必须将測试用例加入到TestSuite中。通过Instrumentation来管理。測试用例的继承关系例如以下图:
除了上面的測试用例外。开发人员还能够通过PerformanceTestCase运行性能測试。注意,ProviderTestCase和ActivityInstrumentationTestCase已经被抛弃,应慎用。
InstrumentationTestRunner主要通过AndroidTestRunner的支持被载入的。AndroidTestRunner的继承关系例如以下图所看到的:
AndroidTestRunner继承了TestListener接口,用来发起和结束測试,生成測试报告,当中发起执行測试的方法为runTest(),这才是JUnit測试的核心。
2)JUnit測试的实现
通过JUnit进行回归測试,须要做例如以下几方面的工作:
构建AndroidMainifest.xml配置文件。
制定InstrumentationTestRunner文件。
构建详细測试代码。
假设是基于源码进行的測试,那么还须要构建Android.mk文件。
(1)构建AndroidMainifest.xml配置文件
和普通project类似的是。JUnit回归測试project须要构建AndroidMainfest.xml配置文件;但和普通project不同的是。JUnit回归測试project没有图形界面,必须声明要用到“android.test.runner"JAR包,声明对应的Instrumentation TestRunner。在通过SDK创建測试project时,AndroidMainfest.xml会自己主动生成,默认的InstrumentationTestRunner为android.testRunner。在某些情况下。开发人员须要定义InstrumentationTestRunner。以下是JUnit回归測试的AndroidMainfest.xml文件的实现:
<mainfest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.calcutor2.tests">
<application>
<use-library android:name=”android.test.runner"/>
<application>
<instrumentation android:name="CalculatorLaunchPerformace" //仅在源码下可用
android:targetPackage="com.android.calculator2"
android:label="Calculator Launch Performance">
</instrumentation>
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.calculator2"
android:label="Calculator Funstional Testset">
</instrumentation>
</manifest>
(2)指定InstrumentationTestRunner文件
InstrumentationTestRunner文件为Junit測试的入口文件,在某些情况下自己定义InstrumentationTestRunner类。最重要的是addTestSuite方法。他将TestCase纳入TestSuite流程,调用对应的方法。步骤例如以下:
public class MusicPlayerFunctionalTestRunner extends InstrumentationTestRunner{
public TestSuite getAllTests(){
TestSuite suite=new InstrumentationTestSuite(this);
suite.addTestSuite(TestSongs.class);
suite.addTestSuite(TestPlaylist.class);
suite.addTestSuite(MusicPlayerStability.class);
retuen suite;
}
public ClassLoader getLoader(){
return MusicPlayerFunctionnalTestRunner.class.getClassLoader();
}
}
(3)构建详细測试代码
为了測试详细的代码,须要依据组件的类型构建对应的測试用例。当中。有两个关键的方法要注意:setUp方法用来构建測试环境,如打开网络链接等;tearDown方法能够确保在进入下一个測试用例前全部资源被销毁并被回收。
对于不同的測试,应该配置不同的測试类型。測试Activity的用例的实现例如以下:
public class SpinnerTest extends ActivityInstrumentationTestCase2<RelativeLayoutStubActivity>{
private Context mTargetContext;
public SpinnerTest(){
super("com.android.cts.stub", RelativeLayoutStubActivity.class);
}
protected void setUp() throws Exception{
super.setUp();
mTargetContext=getInstrumentation().getTargetContext();
}
protected void tearDown() throws Exception{
super.tearDown();
}
public void testGetBaseline(){ //測试项方法必须以test开头
Spinner spinner=new Spinner(mTargetContext);
assertEquals(-1, spinner,getBaseline());
spinner=(Spinner)getActivity().findViewById(R.id.spinnere1);
ArrayAdapter<CharSequence> adapter=ArrayAdapter.createFromResource(mTargetContext, com.android.cts.stub.R.array.string, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
assertTrue(spinner.getBaseline() > 0);
}
}
(4)构建Android.mk文件
在源码中进行编译。必须构建Android.mk文件。和普通Android.mk文件不同,Android.mk文件须要指定LOCAL_MODULE_TAGS为tests。通过LOCAL_JAVA_LIBRARIES变量载入android.test.runner的JAR包。通过LOCAL_INSTRUMENTATION_FOR指定对那个包进行回归測试。以下是进行JUnit回归測试的Android.mk实现:
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS:=tests //指定TAGS为“tests”
LOCAL_JAVA_LIBRARIES:=android.test.runner //载入名为android.test.runner的JAR包
LOCAL_SRC_FILES:=$(call all -java-file-under, src)
LOCAL_PACKAGE_NAMES:=CalculatorTests //包名
LOCAL_INSTRUMENTATION_FOR:=Calculator //指定为那个project做回归測试
include $(BUILD_PACKAGE)
利用JUnit进行回归測试时,假设在控制台上发现例如以下错误提示:
Application does not specify a android.test.InstrumentationTestRunner instrumentation or does not declare uses-library android.test.runner
应检查AndroidManifest.xml的配置是否正确。假设Eclipse弹出提示信息“No tests found with runner 'JUnit 3',应检查測试项目目的Run Configurations中InstrumentationTestRunner的配置是否有误。
(3)CTS兼容性測试
为了防止OEM厂商对Android的定制导致平台的不兼容问题,Google在公布Android版本号的同一时候会公布相关的CTS測试。编译CTS和启动交互CTS控制台的方法例如以下:
cd /path/to/android/root
make cts
cts
运行CTS測试并设置測试參数的方法例如以下:
cts start --plan CTS -p android.os.cts.BuildVersionTest
须要说明的是,为了保持Android系统间的兼容性,Google规定,必须通过CTS兼容性測试,才会授予OEM厂商Android商标和接入Android Market的权利。
CTS兼容性測试对于不少OEM厂商甚至芯片厂商而言,是无法全然通过的。
(4)目标环境測试
在实际的开发过程中。假设临时无法获得物理设备进行測试,考虑到目标环境的差异,应该针对目标物理设备构建自己定义的模拟器。这就是所谓的目标环境測试。
1)硬件配置
硬件的配置能够通过AVD管理器在创建模拟器时设置,当然以后也能够变更。在默认Eclipse工作空间的登录用户为root的情况下,AVD的配置位于\root\.android\avd\avdname.avd文件夹下的config.ini中。
以下是一个config.ini的配置:
hw.lcd.density=160
sdcard.size=200M
skin.name=WVGA800
skin.path=platforms/android-8/skins/WVGA800
hw.cpu.arch=arm
abi.type=armeabi
vm.heapSize=24
image.sysdir.1=platforms/android-8/images
除了以上硬件信息外。开发人员开能够配置摄像头、电池、耳机、麦克风、GPS、LCD背光等。
另外\root\.android\avd\avdname.avd\hardware-qemu.ini中给出了和硬件相关的全然配置。
在模拟器执行时,会检查config.int和hardware-qemu.int中相应的配置项是否一致。当不一致时。依据config.int中的配置来改动hardware-qemu.int中的配置。
2)网络模式
在Android模拟器中,童工AVD管理器还能够模拟网络,如是否支持GSM。通过project的Run Configuration能够选择网络速度,其类型包含Full、GSM、HSCSD、GPRS、EDGE、UMTS、HSDPA等;还能够选择网络延迟。其类型包含None、GPRS、EDGE、UMTS等。这些在开发具有卓越用户体验的移动终端时很重要。
网络配置会在模拟器启动时作为參数传递给模拟器。
3)通话/SNS/模拟
在DDMS中,Andoid支持通话/SMS的測试。
在DDMS面板中,选择好网络状态,如unregistered、home、roaming、searching、denied等。做好速度和延迟配置。之后就可以測试语音、数据和SMS。
关于网络状态和无线接入协议的详细含义。涉及无线通信的协议栈内容。
4)GPS模拟
Android还支持GPS模拟。能够明白指定经纬度,还能够载入GPX、GML等轨迹文件。
4.Android性能优化
尽管眼下的Android终端普遍配置了ARM Cortex A8甚至ARM Cortex A9的双核处理器。可是这并不意味着无须优化性能。Android採取了多种手段来加快应用启动和执行速度。还提供了多个工具供用户分析性能的瓶颈。
在详细的软件开发中,智能终端尽管近今年得到了高速发展,但其所拥有的资源仍然有限。为了降低不必要的开销。有两个原则必须遵守:
不做不必要的事。
不分配不必要的内存。
(1)优化资源读取
依据资源性质的不同,Android对资源文件和SD卡资源进行了优化。
1)资源文件
Android在编译时对描写叙述UI的XML文件进行了优化,这就是开发人员将APK解包后无法打开当中的XML文件的原因。
2)SD卡
对于SD卡中的数据,Android会在设备启动和SD卡插拔时进行增量式扫描。而不是在应用载入资源时进行扫描,这无疑加快了启动速度,其实,这依赖于MediaScanner的实现。当然其也存在局限性。对于文件类型没有包括在MediaFile.java中的文件。MediaScanner就无能为力了。
(2)优化APK载入
Android提供了zipalign。zipalign提供了4字节的边界对齐来映射内存,通过空间换时间的方式来提高APK载入的运行效率。
zipalign的使用方法: zipalign [-c] [-f] [-v] <align> infile.zip outfile.zip //-c表示检查对齐,-f表示强制覆盖已有输出文件
经常使用的zipalign方法例如以下:#zipalign -v 4 infile.zip outfile.zip //-v 代表具体输出。 ”4“表示4字节对齐
对于APK的载入。假设是预制应用,Android会在系统编译后生成后缀为ODEX的优化文件。对于非预制应用。在第一次启动应用时,DEX文件会被优化为DEY文件并放置到\data\dalvik-cache文件夹下,从而加快启动速度。
(3)Dalvik虚拟机
在Android中,出于性能和规避知识产权方面的考虑。Google并没有直接採用CLASS字节码,而是先将Java代码编译成CLASS字节码,然后再将CLASS字节码转化为DEX字节码。在这一过程中,Android会删除冗余,这非常大程度上减小了APK的大小,最后将APK载入到Dalvik虚拟机中。
在CLASS字节码转化为DEX字节码的过程中。Android对冗余信息进行了处理。
另外,通过Dexdump能够查看出APK文件里DEX运行情况,这有助于开发人员优化自己的程序。
(4)TraceView性能分析
TraceView是Android提供的一个调试应用和优化性能的图形界面工具,其包含两个面板。Timeline Panel和Profile Panel。Timeline Panel从时间维度为开发人员提供了优化性能的视角,在Timeliness Panel的左側显示的是不同的线程;Profile Panel从方法调用顺序角度为开发人员提供了优化性能的视角。Profile Panel面板中显示的方法有父方法和子方法,所谓父方法即调用当前方法的方法,所谓子方法即当前方法调用的方法。
为了通过TraceView进行性能分析,首先要在代码中设置跟踪的起点和终点。方法例如以下:
Debug.startMethodTracing("test"); //"test"为生成的trace文件名称
setContentView(R.layout.main);
Debug.stopMethodTracing();
生成的trace文件会被放置在\sdcard文件夹下。当然为了在SD卡下运行写入操作。须要例如以下权限:
<uses-permission>
android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
这里生成的trace文件为test.trace。在将test.trace导出到Linux下后,就可以通过android_sdk_linux/tools/traceview脚本进行性能分析,方法例如以下:
#cd ANDROID_SDK_HOME
#./tools/traceview test.trace
Inclusive表示整个方法从调用到结束的运行时间。包括子方法运行时间;而Exclusive仅表示方法本身的运行时间。
当然因为test.trace仅分析了setContentView的载入过程,从视图上看不出有不论什么性能问题,可是在实际的开发中就不一样了。开发人员要活用TraceView。提升应用的质量。
(5)执行效率优化
为了提升代码的执行效率。必须知道最消耗计算能力的代码段的位置,依据80/20法则,优化最重要的20%的代码就可以大幅提升代码的执行效率。
最直接的观察执行效率的方法是算出代码段在目标环境下执行时间。对应方法例如以下:
long start = System.currentTimeMillis();
long duration = System.currentTimeMillis()-start; //毫秒数
另外,通过观察Logcat显示的虚拟机释放内存的情况,也可观察出计算能力消耗的位置。
在平台方面,为了充分挖掘CPU的性能,Android还针对armv5te进行了优化,充分利用armv5te的运行流水线来提高运行的效率。
在创建新进程时。Android採用了Linux的写时拷贝(Copy on Write)机制。是创建一个新进程很高效。