Android與.Net交互模擬用戶屏幕操作添加APN和網絡4G/3G切換


 

  前幾天接到一個需求,我們的客戶需要對手機網絡接入點進行可用性測試,簡單點說就是需要實現Android上的APN配置的添加,APN切換網絡模式4G/3G/2G切換,我要調研下寫個demo。

  因為是要實現自動化測試,而且得合並到現有的撥測系統(C#項目)成為其中的一個模塊,就需要用C#來驅動Android測試。交互方式上首先想到的是擼個代碼放Android上,定時從服務端獲取任務命令然后執行,嗯,OWIN實現個webapi進行數據交互分分鍾的事情,貌似可行。 不過又想到,我們測試萬一網絡切換壞了,就不能聯網了那就完了。這樣的話,就不能進行任何手機天線端的網絡操作了。接着就想到USB交互 然后找到了這個命令:adb forward tcp:PCPort tcp:Androidport 作用是將當前環境的某個端口與Android的某個端口綁定。這樣Android 內部請求Androidport端口號就和請求PC上的PCPort端口一樣,反之亦然,手機需要打開USB調試。准備寫的時候我又想到,我們做的是無人值守的主動測試,Android一會兒跑過來問問有沒有執行命令,一會兒跑過來問問 感覺有點不大好,麻煩別人還得別人惦記着不是我的性格。。。 balabala一番思想斗爭后決定用socket交互,Android端做服務端,要做啥 過來說下~~

 

  Android的Server端通訊簡要訊碼:

  SCServer :接收連接過來的客戶端,並且保存到ClientManager中

public class SCServer implements Runnable {

    static Boolean Startd = false;
    static Integer Port;
    static ServerSocket serverSocket = null;
    ClientManager clientManager = new ClientManager();

    public SCServer(int port) {
        Port = port;
    }

    @Override
    public void run() {
        if (!Startd) {
            try {
                serverSocket = new ServerSocket(Port);
                Startd = true;
                System.out.println("Startd :" + Port);
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                while (Startd) {
                    Socket socket = serverSocket.accept();
                    clientManager.AddClient(socket);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public void RegistCallBack(String comm, CallBack callBack) {
        CommManager.Add(comm, callBack);
    }

    public void UnRegistCallBack(String comm) {
        CommManager.Remove(comm);
    }

    public void Send(Integer clientID, String comm, Map<String, String> msgDatas) {
        clientManager.SendMsg(clientID, comm, msgDatas);
    }

}
View Code  

  ClientManager:保存所有客戶端,分配唯一編號,線程運行客戶端監聽消息,根據編號找到客戶端Client 發送消息。

public class ClientManager {
    static Integer ClientID=0;
    static Map<Integer, Client> Clients = new HashMap<>();

    public void AddClient(Socket socket) {
        Integer clientID= ClientID++;
        Client clinet = new Client(socket,clientID);
        new Thread(clinet).start();
         Clients.put(clientID, clinet);
    }

    public void SendMsg(Integer clientID, String comm,
            Map<String, String> msgDatas) {
        if (Clients.containsKey(clientID)) {
            Client client = Clients.get(clientID);
            client.SendMsg(comm, msgDatas);
        }
    }
}
View Code  

   Client:數據收發,命令解析。消息的載體是json格式FastJson處理。數據類容轉換為Map<String,String>對應的為C#的Dictionary<string, string>

public class Client implements Runnable {
    private Socket socket;
    private DataOutputStream dos = null;
    private BufferedReader brIs = null;
    private boolean bConnected = false;
    public Integer ClientID = -1;

    public Client(Socket socket, int id) {
        this.socket = socket;
        this.ClientID = id;
    }

    @Override
    public void run() {
        try {
            brIs = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            dos = new DataOutputStream(socket.getOutputStream());
            System.out.println(this.ClientID + " Start");
            bConnected = true;
            while (bConnected) {
                String str = brIs.readLine();
                if(str!=null){
                System.out.println("-------->" + str);
                JSONObject jb = JSON.parseObject(str);
                String msgComm = jb.getString("MsgComm");
                CallBack cb = CommManager.Get(msgComm);
                if (cb != null) {
                    String msgCBComm = jb.getString("MsgCBComm");
                    Map<String, String> msgDatas = (Map<String, String>) JSON.parse(jb.getString("MsgDatas"));
                    cb.execute(ClientID, msgCBComm, msgDatas);
                } else {
                    System.out.println("--->MsgComm:[" + msgComm+ "] Can't Find!");
                }}
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void SendMsg(String comm, String callBackComm,
            Map<String, String> msgDatas) {
        Message msg = new Message();
        msg.MsgCBComm = callBackComm;
        msg.MsgComm = comm;
        msg.MsgDatas = msgDatas;
        String StrJson = JSON.toJSONString(msg);
        System.out.println("<--------"+StrJson);
        try {
            this.dos.writeUTF(StrJson);
            this.dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void SendMsg(String comm, Map<String, String> msgDatas) {
        SendMsg(comm,"",msgDatas);
    }
}
View Code

 

  CommManager:消息命令管理,保存命令關鍵字與回調的處理方法。

public class CommManager {

    static Map<String, CallBack> Comms = new HashMap<String, CallBack>();

    public static void Add(String comm, CallBack callBack) {
        Comms.put(comm, callBack);
    }

    public static CallBack Get(String comm) {
        if (Comms.containsKey(comm)) {
            CallBack callBack = Comms.get(comm);
            return callBack;
        } else {
            return null;
        }
    }
    
    public static void Remove(String comm) {
        Comms.remove(comm);
    }
}
View Code

 

  CallBack:回調接口,返回客戶端ID,消息返回命令,接收的消息

public interface CallBack {
     public void execute(Integer clientID, String callBackComm,
                Map<String, String> msgDatas);  
}
View Code

 

  Message:交互的消息

public class Message {
    public String MsgComm;  //傳過來的命令
    public String MsgCBComm;//回應的命令
    public Map<String,String> MsgDatas=new HashMap<String, String>();//數據
}
View Code

 

調用方式:

 1 final SCServer sc = new SCServer(57641);
 2 
 3         sc.RegistCallBack("DoSth", new CallBack() {
 4             @Override
 5             public void execute(Integer clientID, String callBackComm,Map<String, String> msgDatas) {
 6                     // 執行代碼
 7                 msgDatas.clear();
 8                 msgDatas.put("Result", "OK");
 9                 sc.Send(clientID, callBackComm, msgDatas);
10             }
11         });

 

 

C#的Client端通訊簡要代碼

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace LiteSocket
{


    public class SocketClient
    {
        public bool IsConnected = false;
        private static byte[] result = new byte[2048];
        string IP;
        int Port;
        Thread t_Server;
        Socket clientSocket;
        
        Dictionary<string, Action<string, Dictionary<string, string>>> Comms = new Dictionary<string, Action<string, Dictionary<string, string>>>();


        public SocketClient(string ip, int port)
        {
            IP = ip;
            Port = port;
        }
        public void Close()
        {
            clientSocket.Close();
            t_Server.Abort();
        }

        public bool Connect()
        {
            try
            {
                IPAddress ip = IPAddress.Parse(IP);
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                clientSocket.Connect(new IPEndPoint(ip, Port)); //配置服務器IP與端口  
                t_Server = new Thread(() =>
                  {
                      while (clientSocket.Connected)
                      {
                          try
                          {
                              int receiveLength = clientSocket.Receive(result);
                              if (receiveLength > 0)
                              {
                                  //接收數據處理
                                  string msgStr = Encoding.UTF8.GetString(result, 2, receiveLength - 2);
                                  Console.WriteLine(msgStr);
                                  Message msg = JsonConvert.DeserializeObject<Message>(msgStr);
                                  Action<string, Dictionary<string, string>> action = null;
                                  if (!Comms.TryGetValue(msg.MsgComm, out action))
                                  {
                                      Console.WriteLine("MsgComm :" + msg.MsgComm + " 不存在");
                                  }
                                  else
                                  {
                                      action(msg.MsgCBComm, msg.MsgDatas); //回調
                                  }
                              }
                          }
                          catch (Exception ex)
                          {
                              
                          }
                      }
                  });
                t_Server.IsBackground = false;
                t_Server.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            IsConnected = clientSocket.Connected;
            return IsConnected;
        }

        /// <summary>
        /// 注冊回調方法
        /// </summary>
        /// <param name="Comm">消息命令</param>
        /// <param name="CallBack">回調方法</param>
        public void RegistComm(string Comm, Action<string/*返回消息命令*/, Dictionary<string, string>> CallBack)
        {
            if (!Comms.ContainsKey(Comm))
            {
                Comms.Add(Comm, CallBack);
            }
            else
            {
                Comms[Comm] = CallBack;
            }
        }

        public void UnRegistComm(string Comm)
        {
            if (Comms.ContainsKey(Comm))
            {
                Comms.Remove(Comm);
            }
        }
        /// <summary>
        /// 發送數據給服務端,需要返回,回調響應
        /// </summary>
        /// <param name="comm">命令消息</param>
        /// <param name="callBackComm">返回消息</param>
        /// <param name="msgDatas">消息內容</param>
        public void PostData(string comm, string callBackComm, Dictionary<string, string> msgDatas)
        {
            Message m = new Message();
            m.MsgComm = comm;
            m.MsgCBComm = callBackComm;
            m.MsgDatas = msgDatas;
            string json = JsonConvert.SerializeObject(m);

            Console.WriteLine(json);
            if (clientSocket.Connected)
            {
                clientSocket.Send(Encoding.UTF8.GetBytes(json + "\n"));
            }
            else
            {
                Console.WriteLine("Connected Is Broken");
            }
        }
        /// <summary>
        /// 發送命令給服務端,不需要返回數據
        /// </summary>
        /// <param name="comm"></param>
        /// <param name="msgDatas"></param>
        public void PostData(string comm, Dictionary<string, string> msgDatas)
        {
            PostData(comm, "", msgDatas);
        }

        /// <summary>
        /// 發送命令給服務端,並等待返回的消息。
        /// </summary>
        /// <param name="comm"></param>
        /// <param name="waitSeconds">命令執行超時時間 默認60s</param>
        /// <returns></returns>
        public Dictionary<string, string> SendData(string comm, int waitSeconds = 60)
        {
            return SendData(comm, new Dictionary<string, string>(), waitSeconds);
        }
        /// <summary>
        /// 發送命令和數據給服務端,並等待返回的消息。
        /// </summary>
        /// <param name="comm"></param>
        /// <param name="msgDatas"></param>
        /// <param name="waitSeconds">命令執行超時時間 默認60s</param>
        /// <returns></returns>
        public Dictionary<string, string> SendData(string comm, Dictionary<string, string> msgDatas, int waitSeconds = 60)
        {
            DateTime waitTime = DateTime.Now.AddSeconds(waitSeconds);
            Dictionary<string, string> returnMsgDatas = null;
            string RdComm = RandomStr(8); //隨機生成返回消息命令
            RegistComm(RdComm, (cbkey, data) =>
            {
                returnMsgDatas = data;
            });
            Message m = new Message();
            m.MsgComm = comm;
            m.MsgCBComm = RdComm;
            m.MsgDatas = msgDatas;
            string json = JsonConvert.SerializeObject(m);
            if (clientSocket.Connected)
            {
                clientSocket.Send(Encoding.UTF8.GetBytes(json + "\n"));
            }
            else
            {
                Console.WriteLine("Connect Is Broken");

            }
            //等待返回數據
            double wait = 0.00;
            while (returnMsgDatas == null && wait<=0)
            {
                Thread.Sleep(500);
                wait = (DateTime.Now - waitTime).TotalSeconds;
            }
            UnRegistComm(RdComm); //注銷命令
            return returnMsgDatas;
        }


        public static string CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        /// <summary>
        /// 真·隨機字符串
        /// </summary>
        /// <param name="lenght">長度</param>
        /// <returns></returns>
        public string RandomStr(int lenght)
        {

            StringBuilder sb = new StringBuilder();
            Random r = new Random(Guid.NewGuid().GetHashCode());
            for (int i = 0; i < lenght; i++)
            {
                sb.Append(CHAR[r.Next(25)]);
            }
            return sb.ToString();
        }
    }
}

 

Message:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LiteSocket
{
   public class Message
    {
       public string MsgComm { set; get; }
       public string MsgCBComm { set; get; }
       private Dictionary<string, string> _MsgDatas = new Dictionary<string, string>();
       public Dictionary<string, string> MsgDatas
       {
           get { return _MsgDatas; }
           set { _MsgDatas = value; }
       }

    }
}

 

調用方法:

        SocketClient SC = new SocketClient(ip, port);
            Dictionary<string, string> Dic_doSth = new Dictionary<string, string>();
            Dic_doSth.Add("somethingKey", "somethingValue");
            var result = SC.SendData("DoSth", Dic_doSth);//發送並接收返回數據
            //OR
            SC.RegistComm("SthOver", (rekey, value) => { 
                //處理返回數據
            });
            SC.PostData("DoSth", "SthOver", Dic_doSth); //發送 異步處理返回數據

 

以上的交互完成了,后面就是業務代碼了。APN添加切換 網絡模式切換

網上搜了下,得到一個例子:Android開發之APN網絡切換 心中暗喜:有前輩給出了解決方案,還有代碼實例,這實現起來還不簡單么。照貓畫虎。。。后發現出了個錯:

 No permission to write APN settings:

查詢了一翻發現android 4.0以上對這一權限進行回收了。我們的測試機為小米4,按照網上說的方法進行了 重新系統簽名,系統權限設置均無效,依然會有權限錯誤,中間為了得到android4.4.4的platform.pk8文件還下載了8G的android 4.4.4源碼。可能是MIUI的與android原生的系統簽名不一樣 總是就是要不沒權限 要不安裝不上。 網上還有一種方法是 MM編譯,得在Linux環境下;Eclipse+NDK配置又是很多的配置,看着教程實在感受不到愛了。。。 索性就放棄了這方案 曲線救國的方式來實現需求-----模擬用戶屏幕操作。 adb有個Input命令,可以模擬鍵盤輸入,屏幕點擊,屏幕滑動。

adb shell input keyevent “value”
usage: input ...
       input text <string>
       input keyevent <key code number or name>
       input tap <x> <y>
       input swipe <x1> <y1> <x2> <y2>

 

常用鍵:

input keyevent 3    // Home
input keyevent 4    // Back
input keyevent 19  //Up
input keyevent 20  //Down
input keyevent 21  //Left
input keyevent 22  //Right
input keyevent 23  //Select/Ok
input keyevent 24  //Volume+
input keyevent 25  // Volume-
input keyevent 82  // Menu 菜單

 

 

抄個這段代碼,Android上執行終端命令,Root權限?小米4:—_—

    public static void execShellCmd(String cmd) {

        try {
            // 申請獲取root權限
            Process process = Runtime.getRuntime().exec("su");
            OutputStream outputStream = process.getOutputStream();
            DataOutputStream dataOutputStream = new DataOutputStream(
                    outputStream);
            dataOutputStream.writeBytes(cmd);
            dataOutputStream.flush();
            dataOutputStream.close();
            outputStream.close();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

 

 那么,當我需要添加一個APN的時候:

Android:

        final SCServer sc = new SCServer(57641);
        sc.RegistCallBack("AddApn", new CallBack() {
            @Override
            public void execute(Integer clientID, String callBackComm,
                    Map<String, String> msgDatas) {
                Intent intent = new Intent(Settings.ACTION_APN_SETTINGS);
                startActivity(intent);
         SystemClock.Sleep(1000);
for (int i = 0; i < msgDatas.values().size(); i++) { String strDo = msgDatas.get(i + ""); FoolHand.execShellCmd(strDo); Log.d("strDo", strDo); SystemClock.sleep(1000); } msgDatas.clear(); msgDatas.put("Result", "OK"); sc.Send(clientID, callBackComm, msgDatas); } });

 

 C#:

  public bool AddApn(string Name, string APN)
        {
            Dictionary<string, string> doSth = new Dictionary<string, string>();
            int i = 0;
            doSth.Add((i++).ToString(), "input tap 463 1810");//點擊新建
            doSth.Add((i++).ToString(), "input tap 650 290"); //點擊名稱
            doSth.Add((i++).ToString(), "input text " + Name); //輸入名稱
            doSth.Add((i++).ToString(), "input tap 846 1040");  //點擊確定
            doSth.Add((i++).ToString(), "input tap 650 470");  //點擊APN
            doSth.Add((i++).ToString(), "input text " + APN); //輸入APN
            doSth.Add((i++).ToString(), "input tap 846 1040");  //點擊確定
            doSth.Add((i++).ToString(), "input keyevent 4"); //退出 (彈出保存確認框)
            doSth.Add((i++).ToString(), "input tap 730 1780");  // 確認保存
            var result = SC.SendData("AddApn", doSth);
            if (result["Result"] == "OK")
            {
                return true;
            }
            else
            {
                return false;
            }
        }

 

 效果:

效果

 

sc.RegistCallBack("SetNetMode", new CallBack() {
            @Override
            public void execute(Integer clientID, String callBackComm,
                    Map<String, String> msgDatas) {
                Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
                startActivity(intent);

                for (int i = 0; i < msgDatas.values().size(); i++) {
                    String strDo = msgDatas.get(i + "");
                    FoolHand.execShellCmd(strDo);
                    Log.d("strDo", strDo);
                    SystemClock.sleep(1000);
                }
                msgDatas.clear();
                msgDatas.put("Result", "OK");
                sc.Send(clientID, callBackComm, msgDatas);
            }
        });
切換網絡模式Java
public bool ChangeNetMode(string NetMode)
        {
            Dictionary<string, string> doSth = new Dictionary<string, string>();
            int i = 0;
            doSth.Add((i++).ToString(), "input swipe 640 550 640 1440");  //滑到最頂端
            doSth.Add((i++).ToString(), "input tap 640 430");
            doSth.Add((i++).ToString(), "input tap 640 1040");
            switch (NetMode)
            {
                case "4G":
                    doSth.Add((i++).ToString(), "input tap 640 260");//選擇4G
                    break;
                case "3G":
                    doSth.Add((i++).ToString(), "input tap 640 430");//選擇3G
                    break;
                case "2G":
                    doSth.Add((i++).ToString(), "input tap 640 600");//點擊2G
                    break;
                default:
                    break;
            }
            doSth.Add((i++).ToString(), "input keyevent 4");
            doSth.Add((i++).ToString(), "input keyevent 4");
            // 640   260 430 
            var result = SC.SendData("SetNetMode", doSth);
            if (result["Result"] == "OK")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
切換網絡模式C#

 

 

 

這玩意模擬鍵盤輸入,所以得記住屏幕位置。

這玩意模擬鍵盤輸入,所以不能錄入中文。

源碼:  AndroidAPNSettings

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM