为了开发出商业级的应用程序,大规模的测试是不可避免的,同时为了提高应用程序的运行速度,需要进行必要的优化。在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压力测试时,容易形成孤岛的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命令完成,运行测试的方法为右击工程项,在弹出的菜单中执行run as-android JUnit Test命令,对于具有多个Instrumentation-TestRunner的测试工程,在执行测试时,应该先在工程的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配置文件
和普通工程类似的是,JUnit回归测试工程需要构建AndroidMainfest.xml配置文件;但和普通工程不同的是,JUnit回归测试工程没有图形界面,必须声明要用到“android.test.runner"JAR包,声明相应的Instrumentation TestRunner。在通过SDK创建测试工程时,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 //指定为那个工程做回归测试
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。通过工程的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)机制,是创建一个新进程非常高效。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/u011014707/article/details/46699099