你的位置:首页 > 操作系统

[操作系统]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  // Homeinput keyevent 4  // Backinput keyevent 19 //Upinput keyevent 20 //Downinput keyevent 21 //Leftinput keyevent 22 //Rightinput keyevent 23 //Select/Okinput 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#

 

 

 

这玩意模拟键盘输入,所以得记住屏幕位置。

这玩意模拟键盘输入,所以不能录入中文。

源码稍后附上