Android⾃动化的⼀般⽅法
1
背景
Android⾃动化包含⾃动化测试和第三⽅App的⾃动化运⾏,这⾥的⾃动化测试主要指的是纯粹的⿊盒测试,即在完全不了解代码逻辑的情况下编写的测试⽤例,可以代替⼈⼯完成重复性的⼯作,提⾼效率;⽽第三⽅App的⾃动化指的是为完成某⼀⽬标或获取指定内容⽽进⾏的⾃动化运⾏。随着技术的不断发展,App安全性的不断提升,破解App或者对App完成抓包的成本越来越⾼,通过⾃动化,能够在不破解第三⽅App的情况下获取App中的部分信息。除此,还可以通过⾃动化实现⼀些⼩⼯具,⽐如,⾃动抢红包插件(作者已将代码开源),跳过apk授权页(源码见下⽂)等。
⾃动化需要模拟⽤户⾏为,按照事先设计好的路径,完成固定的流程,⽤户⾏为可能包含启动App、点击、滚动、输⼊、返回等常规操作,⼀个完整的流程是这些事件的有序组合。为了使⾃动化程序更加稳定,即在程序进⼊异常或错误的页⾯时仍然能够正常响应,并逐步回到正常流程,事件也可能是基于页⾯当前状态的。⼀个基本的运⾏顺序应该是,获取页⾯状态,即页⾯名称、页⾯内容、页⾯结构,保存相应状态,如计数器、输出信息等,然后根据页⾯状态和保存状态进⾏相应的操作,然后触发页⾯变化,再获取新的状态,重复这⼀过程。
2
⾃动化⼯具⼀:辅助服务
Android系统中运⾏的每个App都是⼀个单独进程,因⽽除了攻破Android系统,否则不可能在运⾏时获取其他App的运⾏时内存,进⽽不能够直接操作其他App。然⽽,Android系统提供了辅助服务(AccessibilityService),辅助服务是Android系统提供的⼀种设计⽤来帮助残疾⼈的⼯具。作为Android系统提供的⼀种标准服务,辅助服务能够监控Android系统中其他App中发⽣的事件,并可以获取页⾯结构和操作页⾯,还可以进⼀步进⾏语⾳提⽰,从⽽帮助残疾⼈更好的使⽤Android应⽤。能够被监控的事件基本覆盖了App内部发⽣的所有事件,如页⾯的重绘、点击、滚动、焦点变化等。下⾯借助辅助服务来完成⼀个能够⾃动跳过授权页安装apk的⼩⼯具,以说明辅助服务的使⽤⽅式。
⾃动授权apk安装⼩⼯具
安装apk时,会⾸先弹出授权页,这个时候需要⽤户点击安装按钮才能够开始安装。在需要同时安装多个apk的时候,授权过程会显得略为繁琐。因此,可以通过⼀个在授权页⾃动点击安装的⼩⼯具来完成安装。⾸先通过以下代码来向系统提交安装apk的请求:
这⾥使⽤了⼀个test.apk,放到了SD卡的根⽬录下,然后在代码中获取apk路径,转换成URI,设置参数
application/vnd.android.package-archive,启动安装程序。Android系统安装apk的过程是由com.android.packageinstaller这个包来管理的,所以需要监控来⾃这个包的所有事件。辅助服务可以指定要监控App的包名,如下为实现这个⼩⼯具的服务配置,需要放到⼀个单独的xml⽂件中:
辅助服务反馈的事件有很多种,这⾥只选择其中两种,获取的事件是typeWindowStateChanged和typeWindowContentChanged,当窗体状态或窗体内容变化,也就是页⾯发⽣变化的时候,当前辅助服务会收到事件。android:description指定的内容会在开启辅助服务的时候,展⽰在辅助服务设置页⾯。这个配置需要在l中引⼊:
辅助服务在l中的声明需要包含android.permission.BIND_ACCESSIBILITY_SERVICE权限、过滤器和特定的meta-data,配置的xml在meta-data中的android:resource中引⼊。然后定义⼀个服务,继承AccessibilityService,这个是所有辅助服务的基类,所有的辅助服务均由系统管理,这⾥只需要实现特定的接⼝:
⾃定义服务需要实现onAccessibilityEvent(AccessibilityEvent event)来处理监控的App反馈的事件,通过getRootInActiveWindow()来获取页⾯的根节点信息,这个根节点信息是⼀个AccessibilityNodeInfo的实例,可以通过其获得节点及⼦节点的状态信息,如展⽰的⽂字getText()、描述字段getContentDescription(),同时也可以通过这些信息定位⼦节点。节点的点击操作通过performAction这个函数完成。最后,启动系统的辅助服务列表页:
所有的辅助服务都会展⽰在这个列表页中,在辅助服务设置页⾯中,到当前应⽤,进⼊辅助服务配置页,点击开启服务。之后启动安装即可完成⾃动授权过程。在不同Android设备上可能表现不同,这⾥的逻辑是:监控系统安装页⾯的事件,然后在页⾯中到带有安装⽂案的节点,执⾏点击操作。不同设备安装页⾯的页⾯结构可能不尽相同,要想兼容各种各样的Android设备可以使⽤Android SDK提供的层级查看⼯具uiautomatorviewer来分析App的页⾯结构,然后确定搜索和点击路径,如英⽂系统可能点击的⽂案是INSTALL。
辅助服务⾃动化的缺陷
辅助服务在进⾏⾃动化时,存在以下缺陷:
1. 辅助服务本⾝是⼀个Service,这个Service被系统管理,可能因为很多原因挂掉,则⾃动化停⽌。
2. 辅助服务⽆法⾃动化WebView。
3
⾃动化⼯具⼆:uiautomator
辅助服务作为Android系统中的⼀种标准服务,很难通过脚本去配置和控制,也不容易监控。另外⼀个
进⾏⾃动化的思路是通过⾃动化测试⼯具,这要求⾃动化测试⼯具必须是完全⿊盒的,也即在不了解代码逻辑、代码接⼝的前提下,能够顺利的编写⽤例,实现必要的功能。
uiautomator是Google提供的为Android编写UI测试⽤例的⾃动化⼯具,开发时,可以按照如下⽅式使⽤Gradle添加依赖:
也可以直接依赖uiautomator.jar,uiautomator.jar在Android SDK⽬录下的platforms/[android-version]/⽬录中,提供编写⽤例所需要的⼯具类。运⾏⽤例必须在Android系统中,可以打成Java的可执⾏⽂件jar包,也可以打包成Android标准的test apk,执⾏需要依赖ADB⼯具。除此,uiautomator还可以作为获取Android设备页⾯布局的⼯具,Android SDK⽬录tools/bin/下的可执⾏⽂件uiautomatorviewer是uiautomator获取⾸页页⾯布局的图形界⾯版本,能够⽅便的查看页⾯布局,在分析第三⽅App的时候,会经常⽤到。
使⽤命令⾏
uiautomator的测试⽤例可以打包运⾏,也可以通过adb命令运⾏。uiautomator主要⽀持三个命令,runtest⽤来执⾏测试⽤例,dump命令获取页⾯信息,events可以把发⽣的所有的Accessibility事件(即上⽂中辅助服务能够获取和操作的事件)输出。
adb的help命令会详细输出每个指令的具体功能。这⾥⾸先使⽤dump命令,通过脚本,实现基本的⾃动化功能。dump命令会把当前Android设备展⽰的页⾯结构以xml的⽅式输出到⽂件,所以需要做的是解析xml,获取各个节点信息,节点信息会包含位置,之后配合adb 的input指令,完成基本的操作,实现⾃动化。下⾯利⽤dump命令完成简单的⾃动化脚本,脚本使⽤Python编写。⾸先通过adb命令获取页⾯布局:
页⾯布局的xml⽂件会把Android设备的页⾯解析成⼀个由node节点构成的树结构,每个node节点会固定的包含位置、点击、View的类名等信息,如下是⼀个根节点的信息:
节点信息包含了开发中常会⽤到的属性,由此可以看到,虽然限于Android内存管理模型⽆法获取页⾯上节点的实际内存对象,但是仍然可以借助uiautomator来获取节点的状态。bounds是当前节点在屏幕中的位置,为了操作节点,需要使⽤位置信息确定指定View的位置,然后通过adb的input命令完成具体的操作。解析xml,获取节点位置,完成点击、滚动、输⼊等操作:
adb的input的命令⼗分丰富,能够满⾜绝⼤多数的交互操作,tap模拟点击,swipe模拟⼿指在屏幕上移动,text模拟⽂字输⼊,除此⽐较常⽤到的还有keyevent,⽤于模拟按键操作,按键包含了返回键、home键、软键盘以及外接键盘的按键。如果需要⽀持多设备,需要为adb命令加上-s选项,指定设备序列号。通过拼接这些操作,能够完成简单的⾃动化程序。
利⽤dump⽅式的缺陷
dump指令耗时过长,严重影响⾃动化效率。
⽤例⽅式
androidsdk安装步骤
uiautomator的主要功能是⽤来执⾏测试⽤例,测试⽤例使⽤Java编写,开发时依赖Android SDK。可以通过执⾏⽤例的⽅式来进⾏⾃动化操作第三⽅App,⽤例⼀般的开发⽅式是编写⽤例函数,打包成jar或者test apk。写好的⽤例不能在本地直接运⾏,如果是jar包,必须推送到Android设备上通过adb运⾏,如果是test apk,需安装运⾏。test apk是指⽤来执⾏测试的apk,会在build的时候⽣成,如下为SDK中uiautomator⼯具API中UiDevice的部分源码,可以看到源码没有实现,只是提供了接⼝,因此⽤例⽆法脱离设备运⾏。
uiautomator提供了获取设备和页⾯节点信息的API,借助UiDevice来获取设备信息,借助UiObject、UiObject2获取页⾯上的节点信息。
通过⽤例⽅式完成⾃动化程序的步骤为:
1. 分析要⾃动化的App的页⾯结构,设计⾃动化路径
2. 编写⾃动化程序(实际上是⼀个测试⽤例)
打包,push到Android设备上(apk通过adb am instrument命令,jar包通过adb shell uiautomator runtest命令运⾏)
3. 执⾏⾃动化程序
使⽤这种⽅式执⾏⾃动化效率相⽐dump指令⽅式明显有所提⾼,如下为通过这种⽅式⾃动获取当前⽤户已关注列表的程序,使⽤6.6.5版本:
运⾏uiautomator的⽤例需要使⽤AndroidJUnit4,⽤例的⼊⼝⽅法要⽤org.junit.Test这个标签修饰,多个⽤例之间执⾏顺序⽆法保证,所以不能通过多⽤例的⽅式进⾏⾃动化。这⾥⾸先启动的⾸页,然后通过⽂案查的⽅式进⼊列表页⾯,遍历条⽬,通过uiautomatorviewer可以看到名称对应的resourceId是:id/z1(不同版本、渠道的apk可能对应的resourceId是不⼀样的,因为使⽤AndResGuard进⾏了资源混淆),通过UiSelector获取节点,然后通过getText获取名称,之后进⾏滚动。遍历的终⽌条件是:当列表页⾯滚动到底部之后,⼀定会展⽰“xx个”这样的⽂案,检测到这个⽂案,认为列表已经滚到底部。UiObject的对象是长时间有效的,所以当执⾏滚动之后,使⽤listObj对象仍然可以获取到当前ListView的最新节点数量。
最后,打包执⾏。通过adb命令运⾏:
runtest命令通过-c指定要执⾏⽤例的主类路径。instrument命令⽤来执⾏Android的InstrumentationTest,-w选项表明等待直到⽤例运⾏结束,-e表⽰添加key-value形式的参数,设置⽤例的⼊⼝为ample.WeixinAutomator,即⽤例的class路径,最后是⽤来执⾏⽤例的AndroidJUnitRunner的类路径。
⽤例⽅式实现⾃动化的缺陷
⽆法⾃动化WebView
4
集成adb
ADB是Google提供的能够和Android设备进⾏交互的命令⾏⼯具。如上所⽰的⼩程序均是基于顺序的,各个事件按照事先设计好的顺序,⼀个⼀个执⾏,意味着在执⾏过程中,如果不出现意外,能够正常运⾏,但如果出现诸如断⽹、⽹速过慢、⼿机卡顿、app崩溃之类的问题后,⾃动化就会异常停⽌。所以⾃动化想要稳定运⾏,应该是基于页⾯状态的。基于页⾯状态是指,任何⼀个页⾯,不论其当前展⽰的内容是什么,是断⽹还是未断⽹,是否加载失败,都应对应于⼀种状态,程序应该能够处理这种状态,从⽽即使偏离了原有的流程,也能够慢慢的再回到正常的流程。这就要求完整的监控机
制。除此,⾃动化通常运⾏独⽴,不会有⼈⼯实时的监测,也即出现问题,应该能够事后发现和修复问题。这就要求要有完整的⽇志系统。要想实现这些,需要依赖adb。同时,不论是辅助服务还是uiautomator,虽然能⼀定程度的解析WebView的结构,但都⽆法操作WebView。通过adb的input指令,可以模拟⽤户的各种操作,这也使得⾃动化WebView成为可能。
由于⼤多数未root的Android设备都⽆法进⾏远端adb连接,也即⽆法通过远程的⽅式执⾏adb命令,所以这⾥考虑使⽤套接字。
在宿主设备建⽴套接字服务
使⽤adb调试Android设备通常需要使⽤PC或者服务器,这⾥暂且把其称作宿主设备。在宿主设备上建⽴套接字服务,则请求⽅为Android 设备。宿主设备辅助完成⾃动化功能,并实时监控Android设备的⾃动化状态。如果要确保在Android设备上运⾏的程序能够稳定运⾏,应设⽴超时机制,如果超时未收到来⾃Android设备的事件信息,则应进⾏相应的恢复或重启操作。
Android设备本⾝是⼀个Linux系统,若想要在这个Linux系统和宿主设备操作系统之间建⽴套接字通信,⾸先要进⾏端⼝转发。在宿主设备上建⽴套接字服务,Android设备向宿主设备发送请求,则需要把Android设备上的某⼀空闲端⼝上的内容映射到宿主的某⼀空闲端⼝上,如把Android设备的8484接⼝转发到宿主设备的8484端⼝上,需要执⾏:
可以使⽤:
查看已经映射的端⼝。然后在宿主设备上建⽴套接字服务,这⾥仍然使⽤Python:
这⾥为了简便,利⽤Python的SocketServer.ThreadingMixIn启动⼀个多线程处理事件的套接字服务,AndroidCommandHandler这⾥的解析和处理过程会在⼀个单独的线程中进⾏,使⽤双换⾏作为通信的终⽌符,指令接收完毕可以按照⼀些实现定义好的协议去做相应的处理,⽐如执⾏adb操作、记录⽇志等。⼀种典型的应⽤场景是针对WebView,因为辅助服务和uiautomator都⽆法操作WebView,所以可以先在Android设备上通过辅助服务或uiautomator分析WebView的节点结构,然后把节点信息通过套接字发送到宿主设备上,再在宿主设备通过adb的input的命令完成具体的操作。
在Android设备上建⽴套接字服务
在Android设备上建⽴套接字服务,则宿主设备作为请求⽅。这种⽅式可以通过套接字把原本依赖设备的处理过程放到宿主设备上,也即任何⼀次查询和操作都是由宿主设备发起的,这样想要进⾏的⾃动化流程完全可以在宿主设备上编写程序来处理。开源⾃动化测试框架Appium即通过这种⽅式,能够在完全不需要修改源码条件下,执⾏⾃动化测试⽤例,并且,⽀持任意语⾔来编写⾃动化测试⽤例。因为基于这样的结构,任何开发语⾔都能使⽤套接字来和Android设备通信,从⽽获取页⾯信息、操作Android设备。
Appium使⽤Node.js搭建,同时⽀持iOS和Android的⾃动化测试,为Android提供底层接⼝的是appium-android-bootstrap,通过源码,可以看到其是依赖uiautomator实现的。不过Appium框架本质上是⽤来进⾏⾃动化测试的,框架功能强⼤,但是却不能很好的应⽤于第三⽅apk,其主要是⽤于debug版本的apk的。如使⽤Appium尝试⾃动化,在启动Appium服务的时候,会产⽣⼀条错误信息,导致⾃动化停⽌。阅读Appium源码之后,发现原因是Appium在安装apk之前会使⽤aapt命令来获取apk的基本信息,⽽这条命令在的apk 上执⾏失败。即便如此,Appium的思路仍然是值得借鉴的,下⾯简单说明Appium这种利⽤套接字来开放uiautomator接⼝的⽅式。
⾸先需要把宿主设备上的某⼀端⼝内容,全部转发到Android设备上,使⽤adb命令:
由于分析的Appium的源代码,所以使⽤Appium的端⼝号,Appium在宿主设备上的服务默认是使⽤4724作为和Android设备进⾏通信的端⼝。查看已经转发的接⼝:
adb的forward命令和reverse命令⽤法基本⼀致,只是⽅向是相反的,forward是把宿主设备上的端⼝转发到Android设备上,reverse是把Android设备的端⼝转发到宿主设备上。然后利⽤⼀个测试⽤例,启动套接字服务:
Appium在Android设备上启动⼀个套接字服务监听来⾃端⼝4724的所有消息,套接字服务会做⼀个⽆限循环,也即是⼀个持续不会中断的测试⽤例,这个⽤例会在收到来⾃宿主设备的命令时,执⾏相应
的操作。然后是对于消息的处理:
client是⼀个(java.Socket)对象,调⽤accept建⽴套接字连接,获取套接字内容。内容由handleClientData来处
理,handleClientData只是将字符串解析成具体的指令然后调⽤runCommand来执⾏具体的命令:
来⾃宿主设备的指令可以关闭当前服务,也可以执⾏相应的Action。Appium定义的Action有很多,节点相关的节点Action会以element:开头,之后才是具体的Action内容,Appium以这种⽅式来确定该操作是否需要节点信息,Action的内容可能包括节点寻、执⾏具体的点击、滚动、输⼊等等,Appium定义的所有Action如下所⽰:
Appium使⽤的基本都是uiautomator的接⼝。Appium是⽀持WebView的测试⽤例的,只是需要App的WebView开启Debug模式,同样这在第三⽅App上是⽆法做到的,所以针对这种情况,仍然要使⽤套接字通知宿主设备之后,利⽤adb的input⽅式来进⾏操作。
5
总结
如上介绍了⾃动化Android App过程中可能⽤到的⼏个⼯具,将这些⼯具搭配使⽤能够互补的实现原本⽆法实现的功能,同时配合宿主设备,搭建完整的稳定的Android⾃动化系统。经过⼀段时间的实践发现,通过辅助服务来获取事件和分析节点结构的效率是最⾼
的,uiautomator是功能和API最丰富的,⽽脚本和adb命令是调试和控制最⽅便的。通过⾃动化能够做到定期监控其他App的状态,从第三⽅App中获取基本信息,⽽⽆需进⾏复杂繁琐的逆向分析。同时,⾃动化能够很好的解决重复性⼯作,节省⼈⼒,来进⾏更多的⼯作,当然,也能够通过⾃动化完成对⾃⾝App的基本测试,覆盖主要流程,然后可以⽤来作为上线之前的⼀次安全检查。
快,关注这个,⼀起涨姿势~

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。