2019年1月8日 星期二

22-來山寨Monkeyrunner吧 (實作一個弧形拖曳)

各位看官在讀完 sendevent 的介紹後有什麼感想呢?是不是覺得使用 sendevent 有點繁瑣?若用在模擬按鍵的操作上似乎還勉強能夠接受,但一旦要模擬觸控螢幕的操作,比如拖曳,則似乎有些累人。雖然這問題可藉由良好的封裝來解決,但本喵今天就來為各位看官出個蠢主意——山寨一個 Monkeyrunner 吧!!

其實 Monkeyrunner 之所以能夠模擬裝置的輸入是建立在 Monkey 這個隨機測試程式上的,也就是說 Monkeyrunner 提供的舉凡按下按鍵、點擊螢幕、拖曳等動作都僅是將相關的命令傳給 Monkey,因此若要做一個最簡單的類 Monkeyrunner 的程式,咱們只要自己和 Monkey 溝通便好。與 Monkey 的溝通流程如下:
順序 動作 說明
1 建立 PC 端與 Android 端的連接埠映射
因 PC 上的程式無法與 Android 裝置上的程式直接通訊,所以必須建立一個連接埠映射,如此資料便可在 PC 上的連接埠與 Android 上的連接埠間互相傳輸。
Monkeyrunner 使用的連接埠為 12345,使用 adb forward <LOCAL> <REMOTE> 指令來映射:
adb forward tcp:12345 tcp:12345
2 啟動 Monkey 以監聽連接埠 adb shell "monkey --port 12345"
3 建立與 Monkey 的 socket 連線 連線位址為本機端,即 127.0.0.1
4 模擬所需輸入
可參考 ChimpManager.java 裡的輸入指令用法,
或看 MonkeySourceNetwork.java 裡的 COMMAND_MAP,它儲存 Monkey 可接受的指令。
5 關閉 socket 連線 關閉連線前記得結束 Monkey(送出 quit 命令)。

咱們以 C# 來實作一個弧形拖曳的模擬吧!
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace test_adb
{
    class Program
    {
        const int PORT = 12345;
        static Process s_monkeyProc;


        static void Main(string[] args)
        {
            Forward();
            RunMonkey();
            BuildSocket();
            RemoveForward();

            Console.Write("\nEnter any key to exit.");
            Console.ReadKey();
        }


        static void Forward()
        {
            var info = new ProcessStartInfo()
            {
                FileName = "adb.exe",
                Arguments = "forward tcp:" + PORT + " tcp:" + PORT
            };

            var proc = new Process()
            {
                StartInfo = info
            };

            proc.Start();
            proc.WaitForExit();
        }


        static void RemoveForward()
        {
            var info = new ProcessStartInfo()
            {
                FileName = "adb.exe",
                Arguments = "forward --remove tcp:" + PORT
            };

            var proc = new Process()
            {
                StartInfo = info
            };

            proc.Start();
            proc.WaitForExit();
        }


        static void RunMonkey()
        {
            new Thread(() =>
            {
                var info = new ProcessStartInfo()
                {
                    FileName = "adb.exe",
                    Arguments = "shell \"monkey --port " + PORT + "\""
                };

                s_monkeyProc = new Process()
                {
                    StartInfo = info
                };

                s_monkeyProc.Start();
            }).Start();
        }


        static void BuildSocket()
        {
            Thread.Sleep(2000);

            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            var remote = new IPEndPoint(IPAddress.Parse("127.0.0.1"), PORT);

            try
            {
                socket.Connect(remote);
                Wake(socket);
                TestActions(socket);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                Quit(socket);

                socket.Shutdown(SocketShutdown.Both);
                socket.Close();

                if (!s_monkeyProc.HasExited)
                {
                    Console.WriteLine("Monkey not terminated. Wait 5 sec.");
                    s_monkeyProc.WaitForExit(5000);

                    if (!s_monkeyProc.HasExited)
                    {
                        Console.WriteLine("Failed to terminate Monkey.");
                        s_monkeyProc.Kill();
                    }
                }

                Console.WriteLine(s_monkeyProc.ExitCode);
                s_monkeyProc.Close();
            }
        }


        static void TestActions(Socket socket)
        {
            int ox = 620;
            int oy = 150;
            int x = ox;
            int y = oy;
            int r = 360;

            TouchDown(socket, x, y);
            Thread.Sleep(1000);

            for (int i = 0; i < 50; ++i)
            {
                x = ox - (int)(r * Math.Sin(i * Math.PI / 180));
                y = oy + r - (int)(r * Math.Cos(i * Math.PI / 180));
                TouchMove(socket, x, y);
                Thread.Sleep(10);
            }

            TouchUp(socket, x, y);
        }


        static void Quit(Socket socket)
        {
            SendRequest(socket, "quit");
            Thread.Sleep(1000);
        }


        static void Wake(Socket socket)
        {
            SendRequest(socket, "wake");
            Thread.Sleep(1000);
        }


        static void TouchDown(Socket socket, int x, int y)
        {
            SendRequest(socket, "touch down " + x + " " + y);
        }


        static void TouchMove(Socket socket, int x, int y)
        {
            SendRequest(socket, "touch move " + x + " " + y);
        }


        static void TouchUp(Socket socket, int x, int y)
        {
            SendRequest(socket, "touch up " + x + " " + y);
        }


        static void SendRequest(Socket socket, string cmd)
        {
            Console.WriteLine("Send: " + cmd);

            var request = Encoding.ASCII.GetBytes(cmd + "\n");
            var response = new byte[32];

            socket.Send(request);

            int len = socket.Receive(response);

            Console.WriteLine("Receive: " + Encoding.ASCII.GetString(response, 0, len));
        }
    }
}


「看起來很簡單嘛~那長按按鍵也沒問題吧!」那邊有位看官吆喝道!
「其實長按按鍵是做不到的,這大概是 Monkey 的設計問題吧。。」本喵怯怯地說道 >__<
「那自己和 Monkey 溝通有啥鳥用阿!!」台下看官爆出陣陣不滿!
「ㄟ。。本喵其實也不知道這有什麼用啦 = =a」

沒有留言:

張貼留言