当前位置: 代码迷 >> Android >> Android与.Net交互模拟用户银屏操作添加APN和网络4G/3G切换
  详细解决方案

Android与.Net交互模拟用户银屏操作添加APN和网络4G/3G切换

热度:57   发布时间:2016-04-28 01:16:15.0
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#

 

 

 

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

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

源码:  AndroidAPNSettings

 

4楼韩之一
像2楼说的,换个机型就挂了,可以把设置点击坐标的方法可以由用户自己设定来解决这个问题
Re: yesicoo
@韩之一,是的,可以弄成按键精灵了,,public bool AddApn(string Name, string APN) { Dictionarylt;string, stringgt; doSth = new Dictionarylt;string, stringgt;(); int i = 0; doSth.Add((i++).ToString(), quot;input tap 463 1810quot;);//点击新建 doSth.Add((i++).ToString(), quot;input tap 650 290quot;); //点击名称 doSth.Add((i++).ToString(), quot;input text quot; + Name); //输入名称 doSth.Add((i++).ToString(), quot;input tap 846 1040quot;); //点击确定 doSth.Add((i++).ToString(), quot;input tap 650 470quot;); //点击APN doSth.Add((i++).ToString(), quot;input text quot; + APN); //输入APN doSth.Add((i++).ToString(), quot;input tap 846 1040quot;); //点击确定 doSth.Add((i++).ToString(), quot;input keyevent 4quot;); //退出 (弹出保存确认框) doSth.Add((i++).ToString(), quot;input tap 730 1780quot;); // 确认保存 var result = SC.SendData(quot;AddApnquot;, doSth); if (result[quot;Resultquot;] == quot;OKquot;) { return true;
3楼cherver
挺好的,收藏了。
Re: yesicoo
@cherver,谢谢
2楼Martell XO
这个,换个机型就死翘翘了。
Re: yesicoo
@Martell XO,是的 屏幕点击嘛~
1楼韩之一
这个是不是可以当android版的按键精灵来用?,就按楼主的方式 来,,在自己用winform写和套用来定义执行什么操作的语法,用可视化操作的方式来设定,设定完成后,执行,就发到手机上了,执行,执行效果就像图上显示的那样,,好像在自己执行
  相关解决方案