基于反编译技术的Android应用自动化测试方案
2019-03-29张茂凡周志寰
甘 佳 张茂凡 周志寰 任 想 潘 娅
(1.西南科技大学计算机科学与技术学院 四川绵阳 621010;2.西南科技大学计算机应用研究所 四川绵阳 621010)
在移动互联网的发展过程中,Android系统因其强大的可交互性和开源性,逐渐占领了移动终端的市场。据调研机构Gartner全球智能手机销售报告显示,2018年第一季度手机市场中,Android系统的市场份额高达85.9%[1]。但也因其开源性,Android终端应用滥用手机硬件资源导致手机卡顿,影响用户体验。由于Android系统碎片化严重,设备性能也参差不齐,给应用的稳定流畅运行带来了巨大挑战。因此,在应用正式上线前,应对其进行尽可能完善的性能测试。目前,针对手机应用的性能测试点包括有CPU、内存、流量、电量、网络、启动耗时、过度绘制等。但是在启动耗时、过度绘制方面,通常只能获取大致的性能数据,不能对问题分析进行准确定位。
国内移动互联网公司针对Android应用的性能测试,主要分为两种方案:一是借助工具,实时采集手机性能数据;二是在测试APK中插入测试桩,获取性能数据。第一种方案的主要代表有腾讯的GT 和讯飞的iTest 等。GT提供了两套性能测试解决方案,其中一种是将其提供的SDK嵌入APK内,从而达到开发调试的目的,另一种是提供测试工具的APK,与待测应用一起安装在手机上,操作待测应用,实时展示性能测试结果。iTest工具与GT的后一套方案类似,也是提供测试工具APK,对待测应用进行实时测试及记录。使用测试工具APK来进行性能测试的方案虽然能够实时获取手机性能数据,但发现问题后测试人员不能快速地进行问题定位。为了更好地解决问题定位,Google为Android开发者提供了许多获取性能数据的API,开发者只需在测试APK中调用API,便能进行性能问题定位。但是这种方案缺点在于要在源码中插入测试API调用代码,而插入测试代码的APK只能作为测试版本,不能发布到应用商店。
本文提出一种性能自动化测试方案,利用反编译技术获得应用源码,在源码中插入测试桩函数,重新打包后进行性能测试,从而获取更精准的性能测试数据文件,自动解析后展示异常数据,能够对测试工作进行有效辅助,提高测试效率。
1 自动化测试方案设计
为了更好地定位性能问题,本文提出将反编译技术应用到测试过程中,测试设计流程如图1所示。测试人员只要提供待测APK以及测试桩的Java代码,然后运行本框架,最后只需等待测试结果即可。
整个方案分为三部分:反编译APK并插入代码、运行APK获取并解析性能数据、展示数据及定位问题。其中,第一部分是本文的核心工作,主要利用反编译技术获得APK源码,根据需要测试Activity名称,将测试桩函数插入原APK中的对应文件中,并重新编译生成测试包。
图1 方案设计流程图Fig.1 Flow chart of the scheme design
2 关键部分实现
本文所提出的性能自动化测试方案,在具体框架实现及应用时有七大模块,包括:
(1)反编译APK;
(2)将Java代码编译为smali代码;
(3)插入smali代码后对APK重新打包、重签名;
(4)编写功能自动化测试脚本;
(5)运行APK获取性能数据文件;
(6)解析性能数据文件;
(7)可视化性能数据定位问题。
反编译APK并插入测试桩是本方案的关键部分,整个流程如图2所示。
2.1 反编译APK
在Android Studio等编译器中,开发完成的Android应用点击运行,编译器会将应用打包成一个后缀为.apk的文件。该文件可以在Android系统的手机或者平板电脑等终端上下载并安装。一般情况下,Android应用被打包成.apk文件之后,就无法逆向该文件得到应用的源代码以及资源文件。但在APK未加壳的情况下,通过一些开发者工具,还是可以将APK进行反编译并得到开发这个应用时使用的资源文件(如图片等) 、源代码等。
图2 反编译APK插入测试桩流程图Fig.2 Flow chart of the decompiled APK inserted intest stub
本文中要用到的反编译工具是Google公司提供的ApkTool,将待测APK反编译成 .smali文件。工具的使用方法可以参考官网 ,方案中将ApkTool反编译命令集成在框架中自动执行。在使用过程中需要注意以下问题:
(1)ApkTool尽量使用最新版本。ApkTool工具新老版本的执行命令存在差异,并且旧版本ApkTool容易导致反编译过程中出现报错“Exception in thread "main" brut.androlib.AndrolibException”。
(2)尽量使用未代码混淆的APK进行测试。混淆代码后的APK,进行反编译后,不能完全复原其代码,会对接下来的测试桩插入造成影响。
2.2 将Java测试桩代码编译为smali代码
在前面的测试思路中也提到,本文需要测试人员提供Java测试桩代码,再由框架自动编译为smali代码插入到源码中。在通过命令行编译Android Java代码文件过程中,如果Java文件引用到许多第三方库文件,在命令编译过程中就会报很多错误。为了编译顺利通过,本文建议将测试桩代码中所有引用到的Jar包都放在一个文件夹中,然后再编译命令中引入该文件夹即可。
在本文方案中,Java文件编译为smali文件需要通过以下3个步骤:
(1)通过“javac test.java”命令将 .java文件转化为 .class文件;
(2)通过dx.jar工具命令“dx --dex --output=test.dextest.class”将 .class文件转换为 .dex文件;
(3)通过baksmali.jar工具命令“java -jar baksmali.jar test.dex”将 .dex文件转换成 .smali文件。
2.3 自动化测试流程实现
前面的两部分介绍了在本方案中是如何实现反编译APK为Smali代码以及将Java代码编译成smali代码。在1.2节中的测试思路中也提及,本文提出的方案支持自动化测试,能够自动化展示性能测试情况,便于定位问题。下面就本文如何实现自动化测试进行介绍。
首先,本框架整体使用Python语言编写,提供配置文件供测试人员填写,因此测试人员无需关心框架内部实现细节,而框架只需要解析配置文件来获得APK信息、Java测试桩代码等,同时,在配置文件中也配置了代码所要插入的Activity以及函数位置,这是实现自动化测试的第一步,紧接着便可自动化进行反编译、插桩等一系列活动。配置文件内容如图3所示。
图3 框架配置文件Fig.3 Configuration file of framework
其次,在本框架下进行性能测试时直接运行自动化测试脚本。经过实践本框架支持任意性能自动化测试框架,如UIautomator,monkey,QT4A等,保证了性能测试的自动化执行,无需人工干预。随着性能测试的不断深入,数据也在不断积累,最终本框架将获取到的两类性能数据自动进行数据可视化,呈现在Web页面上。
综上所述,本框架在测试过程中不需要进行人工干预,能够实现自动化测试并展示结果。
3 方案应用及结果
为了验证本文提出的方案能够对定位性能问题及优化提供帮助,本文针对启动耗时、过度绘制两个专项测试点,选择开源游戏应用《2048》进行实验。下面首先介绍测试点的测试基本原理再阐述具体的实验过程和结果。方案如图4所示。
图4 实验方案设计Fig. 4 Experiment scheme design
3.1 测试原理
(1)启动耗时测试
启动耗时是指在Android应用中从零开始到页面Activity加载完成的时间。Google官方文档中给出两种测试方法:第一种是使用命令“adb shell am start-w packagename/activity”来启动耗时相关参考值,如图5所示。该命令获取的信息中,各字段及解释如表1所示。
图5 adb获取页面启动耗时结果Fig.5 Results of starting time consumption via adb
该方法虽然能够获取到应用的启动耗时等信息,但对应用启动耗时问题定位没有帮助。第二种是函数耗时的分析方法:在APK源代码中,通过插入测试桩函数,输出一个后缀为 .trace的二进制文件,该文件记录了函数的耗时、调用次数、隶属线程等信息;同时,借助Android SDK提供的dmtracedump工具,将 .trace二进制文件解析成可读的编码格式,从可读文件提取出关键字来获得函数耗时等信息。
表1 APK启动耗时字段及含义Table1 Explanation fields of APK starting time consumption
(2)过度绘制测试
过度绘制(Overdraw)描述的是手机和平板等移动终端屏幕上同一个像素在同一帧的时间内被绘制了多次。在一个多层次重叠的UI结构中,如果用户不可见的界面也存在绘制的行为,一些像素区域会被多次绘制,因此会造成CPU和GPU硬件资源不必要的浪费。在 Android 系统的开发者模式中,有调试 GPU 过度绘制的开关,打开该开关后Android手机屏幕会有蓝色、绿色等颜色的色块,不同颜色色块代表过度绘制严重程度的不同。在Android系统中没有直接提供接口获取屏幕过度绘制计数,但Framework/base/core/Java/android/view/HardwareRender.java中的GLRenderer内部类提供了drawOverDrawCounter方法(如图6所示),调用该方法可以在手机屏幕左下角显示出当前UI界面过度绘制次数(即overdraw参数的值),因此获取overdraw参数的值即可知道过度绘制的次数。
图6 HardwareRender类关键代码Fig.6 The key codes in HarwareRender class
综上所述,在应用本方案进行测试时,我们将按照这样的基本测试原理来编写测试桩函数,进行实验验证。
3.2 实验过程
按照实验方案设计,实验按照以下步骤进行:
(1)编写功能自动化脚本运行应用,通过本方案能够展现应用启动耗时的情况,结果如图7所示。从图中可以直观看出调用函数com.demo.game2048publish.SplashActivity.waitTime()消耗6 734 us,远高于其它函数,导致应用启动耗时较长;
图7 《2048》测试结果Fig.7 Test result of 2048
(2)通过测试发现的问题,开发人员可以根据函数名定位到应用源码函数调用的位置,进而发现该函数在启动页面的onCreate()方法中被循环调用了1 000次。
(3)在应用源码中修复该问题,这里将源码中的循环调用删除,改成只调用一次waitTime()函数。
(4)修改应用源码后重新打包生成 .apk文件,使用同样的运行脚本再进行一次性能数据的获取,得到的启动耗时结果如图8所示,可以看到较优化前,该函数的调用耗时有明显下降,约为优化前耗时的1/1000。
为了更好地说明应用的启动耗时在优化前后的变化,实验在同一部测试机上使用adb命令的方法3次获取应用的启动耗时TotalTime值,结果如表2所示。
图8 《2048》应用启动耗时优化后结果Fig. 8 Result of 2048 starting time consumption after optimization
测试次数启动耗时优化前/ms启动耗时优化后/ms第一次265257第二次266255第三次265258
从表2可以看出,《2048》应用的启动耗时从优化前的265.3 ms降低为优化后的256.6 ms,启动耗时优化后降低了8.7 ms。
以上实验表明,本文提出的启动耗时测试方案能够发现并准确定位到应用的启动耗时问题,问题修复后得到的启动耗时数据有明显下降进一步说明定位的有效性。
同理,对过度绘制问题的定位,在本方案下进行测试后可以清楚看到哪个函数过度绘制问题严重(如图9所示),可以方便开发人员准确定位。
图9 过度绘制可视化图Fig. 9 Visualization of overdrawing
4 总结
本文针对Android应用性能测试过程中不易定位的问题现状,提出了一套自动化测试方案,基于反编译技术,向APK源码中插入测试桩函数,并重新生成测试包,辅助测试人员进行性能测试。以启动耗时、过度绘制为例,应用本方案进行性能测试。通过在某两款应用上进行实验,表明相较传统的方法,该方案能够更为精准地发现并定位启动耗时和过度绘制的问题。在编译APK包时,可同时生成测试包和正式包,规避了测试桩函数人工插入的成本问题,也能解决单独编译测试包的耗时问题。该方案目前虽只在启动耗时、过度绘制两点上进行应用,但反编译源码插入测试桩函数测试同样也适用于其它专项测试,稍作调整后具备一定的通用性。