前几天接到一个需求,我们的客户需要对手机网络接入点进行可用性测试,简单点说就是需要实现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);
} }
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);
}
}
}
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);
}
}
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);
}
}
CallBack:回调接口,返回客户端ID,消息返回命令,接收的消息
public interface CallBack {
public void execute(Integer clientID, String callBackComm,
Map<String, String> msgDatas);
}
Message:交互的消息
public class Message {
public String MsgComm; //传过来的命令
public String MsgCBComm;//回应的命令
public Map<String,String> MsgDatas=new HashMap<String, String>();//数据
}
调用方式:
final SCServer sc = new SCServer(57641); sc.RegistCallBack("DoSth", new CallBack() {
@Override
public void execute(Integer clientID, String callBackComm,Map<String, String> msgDatas) {
// 执行代码
msgDatas.clear();
msgDatas.put("Result", "OK");
sc.Send(clientID, callBackComm, msgDatas);
}
});
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[];
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 > )
{
//接收数据处理
string msgStr = Encoding.UTF8.GetString(result, , receiveLength - );
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 = )
{
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 = )
{
DateTime waitTime = DateTime.Now.AddSeconds(waitSeconds);
Dictionary<string, string> returnMsgDatas = null;
string RdComm = RandomStr(); //随机生成返回消息命令
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<=)
{
Thread.Sleep();
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 = ; i < lenght; i++)
{
sb.Append(CHAR[r.Next()]);
}
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 = ;
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 = ;
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#
这玩意模拟键盘输入,所以得记住屏幕位置。
这玩意模拟键盘输入,所以不能录入中文。