悲催的调试ASTER与monkey的经验

来源:互联网 发布:无线通信与网络 pdf 编辑:程序博客网 时间:2024/04/29 02:42

    刚工作不久,能力有限,经验也不足,志在工作中成长,与大家分享工作经验,有错误的地方请大家指出! 非常感谢! 

    ASTER是台湾0xlab开发的一款开源的自动化测试工具,该工具运行在PC机上,与android设备上的monkey进行通讯,通讯方式使用socket。当然,ASTER能与android设备通讯的前提是adb能正常工作。当ASTER与设备连接上后,会用ddms把设备的屏幕截取过来并基本上实现同步显示,然后测试人员就可以在ASTER上(PC端)添加一系列动作去控制设备,最后得出测试结果。

    我遇到的问题是,ASTER一直连接不上我们的设备,从logcat看到的信息是在启动monkey的时候,monkey会随机的挂掉,所谓随机的挂掉是指它不是每次都在相同地方退出。问题可能在于ASTER,也可能在于android设备。我的第一反应是问题出现在我们的设备,因为别人发布出来的工具在正确性方面有一定的保证。可惜的是,老大认为问题在于ASTER,硬是要求我去调ASTER。所以我就把ASTER的代码翻了一遍,从它如何识别设备,如何跟设备建立连接,如何启动设备的monkey,如何截图等等,一步一步地调试过来。实话实说,这源码我整整看了一个多星期才有点了解。不过它的源码也不少,源码包下载下来有64M!可惜悲剧的是,用了这么多时间还没找到问题的所在,确实不淡定啊!

    然后我就跑去看monkey的代码,monkey在启动时,要唤醒系统,由于我们的power管理系统还没完善,问题有可能出现在这里,所以我就把唤醒的代码屏蔽了,可是ASTER在启动monkey时,monkey还是会中途退出。没办法,只能继续debug!难道这就是传说中的“A bug a day, keep girls away” ?哎,命苦的程序猿啊!Anyway,生命不息,奋斗不止。在monkey中,我看到它启动了一个socket,当作服务端。原来,ASTER通过adb与设备连接后,会启动monkey当作服务端,然后自己也创建一个客户端的socket去与monkey的socket连接。于是,我就怀疑socket通讯有问题,ASTER与monkey使用的socket是java层的。等一下!!Java!!?FT!!哥当初为了摆脱java才转去学嵌入式,现在可好了,又栽在这货手上!!只能死马当活马医了!!硬着头皮补一下java的socket基础知识!写了一个socket通讯的测试小程序(程序后面给大家贴上),客户端(PC)能正常发送数据到服务端(设备),排除了socket出现问题的可能性。问题又不在于这,情以何堪,时间都快过去两周了!

    转去看logcat打印的信息,只希望能在这找到有用的东西了。发现每次monkey退出时,都会打印一行“untracked pid xx exited”,这行信息是从init进程打印出来的。还是研究研究init吧!init是android系统的主进程,它管理着系统子进程资源的分配和回收。打印刚刚信息的源码在system/core/init/signal_handler.c,看来跟信号有关,所以调查是什么信号令到monkey退出。后来发现是这个信号SIGHUP,没怎么接触linux内核,不晓得这个信号表示什么意思!网上因该有,度娘和谷哥,自己选吧!最后还是选择了度娘,谷哥半天刷不开网页啊!!有木有!!!百科是这样解释SIGHUP的。SIGHUP会在以下3种情况下被发送给相应的进程:  

1、终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程)  

2、session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程  

3、若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。 

系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有屏蔽该信号,当收到该信号时,进程就会退出。

    总的来说就是接收到SIGHUP信号的进程就会退出,那么究竟是谁给monkey发送了这个信号!在android设备的终端ps一下,能够看见所有进程的pid和父进程的pid,由此发现monkey进程的父进程竟然是sh。原来ASTER是通过adb shell启动monkey的,所以就出现了以上状况!好,找到问题所在了,原因就是monkey还没完全启动,它的父进程就已经退出了,父进程退出时向它发送了一个可恶的SIGHUP信号,表示说,儿子,来吧,跟老爸走吧!可怜无知的monkey就这样给它老爸陪葬了!如何拯救monkey!?简单,让它忽视老爸的SIGHUP信号就行了,如何忽视?在启动monkey的脚本中加入trap "" HUP。我发现其他的android版本都有这一句话在里面,就我们的没有,WTF!折腾了两个星期!就是这么一句话引起了血案!或许大多数的android版本加入这一句话就能正常工作了,但是我们的还不行!贱人就是矫情!!因为我们的shell版本不支持trap这条指令!更换busybox?kernel团队的人忙的很,哪有时间鸟你!只能自己想办法!原来还有另外一个方法可以忽视SIGHUP信号。就是在父进程启动子进程时加入nohup命令!ASTER是在aster/src/com/android/chimpchat/adb/AdbChimpDevice.java里面启动monkey的,找到String command = "monkey --port " + port,把它改成String command = "nohup monkey --port " + port就行了!当然,这样做就有一个弊端,就是必须自己把ASTER的源码下载下来编译!

    monkey父进程都已经退出了,为什么忽视掉SIGHUP信号后,它就不会退出呢?原来在linux系统中,父进程死去后,该父进程创建的所有未退出的子进程都会变成孤儿进程,init会把它们收为养子!这样,monkey最后的父进程就变成了init!

    经过这么一翻折腾后,ASTER终于跟monkey连上了,看着熟悉的界面,终于可以睡个安稳觉了!



这里贴上文中所提到的测试socket的java小程序:

MonkeyServer.java

import java.io.*;import java.net.*;public class MonkeyServer {        private int port=12345;        private ServerSocket serverSocket;        private Socket socket=null;        private BufferedReader input;        private PrintWriter output;public MonkeyServer() throws IOException {        serverSocket = new ServerSocket(port,0,InetAddress.getLocalHost());        System.out.println("start server...");}public void service() throws IOException {        socket = serverSocket.accept();        System.out.println("New connection accepted " +socket.getInetAddress() + ":" +socket.getPort());        input = new BufferedReader(new InputStreamReader(socket.getInputStream()));        output = new PrintWriter(socket.getOutputStream(), true);        String read = input.readLine();        if(read!=null){                        System.out.println(read);                        }        socket.close();}public static void main(String args[])throws Exception {        MonkeyServer server=new MonkeyServer();        server.service();    }}

MonkeyClient.java

import java.net.*;import java.io.*;public class MonkeyClient {        public static void main(String args[])throws Exception{        String host="127.0.0.1";        int port=12345;        Socket socket=null;        BufferedWriter output;        BufferedReader input;        String line;        socket = new Socket(InetAddress.getByName(host), port);        System.out.println("start client...");        if (socket!=null){                System.out.println("connection successful...");        }        output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));        input = new BufferedReader(new InputStreamReader(socket.getInputStream()));        output.write("Client has sent a message to server...".trim()+"\n");        Thread.sleep(1000);        output.close();        input.close(); }}

MonkeyServer是运行在设备上的,除了javac编译外,还要dx一下,我写了个shell脚本来处理dx这一步:

#!/bin/bashfor j in `ls *.java`do        javac $jdoneif [ ! -e classes ] ; then        mkdir -p classesficp *.class classes/dx --dex --output=Main.jar classes

MonkeyClient运行在PC上,按照一般的java文件编译就能执行了!

先在设备上运行MonkeyServer,然后在PC运行MonkeyClient,设备端会打印“Client has sent a message to server...”。至于服务端怎么向client端发送数据,暂时还没去研究!呵呵!


原创粉丝点击