最近公司给了一个任务,需要开发一个安卓自动化测试工具,使用USB数据线连接pc端和设备,配合pc端软件一键验证设备功能是否正常。
为什么要开发这样的一个软件,主要是公司设备大量生产出来以后,要测试设备功能(设备电压、串口通信、拍照、SIM卡通信等功能),手动一个一个功能测试浪费时间,所以就需要实现自动化测试。和同事商量了下,使用adb forward转发tcp端口进行设备和计算机之间的通信。
熟悉安卓开发的都知道ADB 是Android SDK自带最常用且功能强大的工具,它的主要功能:
安装、卸载应用:adb install app.apk,adb uninstall com.example.app
设备重启、恢复出厂设置:adb reboot,adb reboot recovery
查看日志:adb logcat
文件传输:adb push local_file remote_file,adb pull remote_file local_file
端口转发:adb forward tcp:8001 tcp:8001
在 Android 开发中,ADB (Android Debug Bridge) 的 adb forward
命令允许在开发计算机和连接的 Android 设备之间建立端口转发,从而实现服务端和客户端之间的通信。
adb forward
命令的基本语法是:
adb forward <local> <remote>
-
<local>
: 开发计算机上的端口 -
<remote>
: Android 设备上的端口
常见用途
1. TCP 端口转发
最常见的用法是将本地端口转发到设备端口:
adb forward tcp:8001 tcp:8001
这会将计算机的 8001 端口转发到设备的 8001 端口。
2、查看和移除转发
-
查看所有活跃的转发:
adb forward --list
-
移除特定转发:
adb forward --remove tcp:8001
-
移除所有转发:
adb forward --remove-all
言归正传,看看代码实现。计算机作为客户端,使用java实现,设备Android端作为服务端
Java服务端和Android客户端通信
Java客户端代码
public static void main(String[] args) {
// 先设置ADB端口转发
if (!setupForward(8001, 8001)) {
System.err.println("无法建立ADB端口转发");
return;
}
// 连接到转发端口
try (Socket socket = new Socket("localhost", 8001);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
BufferedReader stdIn = new BufferedReader(
new InputStreamReader(System.in))) {
System.out.println("已连接到Android服务端,输入消息开始通信...");
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("服务端回应: " + in.readLine());
// TODO 实现自己的业务逻辑
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 移除端口转发
try {
removeForward(8001);
} catch (AdbException e) {
throw new RuntimeException(e);
}
}
}
/**
* 设置端口转发(增强版)
*/
public static boolean setupForward(int localPort, int remotePort) {
try {
String result = executeAdbCommand(
String.format("forward tcp:%d tcp:%d", localPort, remotePort), 5);
System.out.println("ADB Forward Result: " + result);
return true;
} catch (Exception e) {
System.err.println("设置端口转发失败: " + e.getMessage());
return false;
}
}
/**
* 执行ADB命令
*/
public static String executeAdbCommand(String... commands) throws AdbException {
List<String> commandList = new ArrayList<>();
commandList.add("adb");
commandList.addAll(Arrays.asList(commands));
try {
ProcessBuilder pb = new ProcessBuilder(commandList)
.redirectErrorStream(true);
Process process = pb.start();
// 使用Future和ExecutorService实现超时控制
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
return output.toString();
}
});
try {
return future.get(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
} catch (TimeoutException e) {
process.destroy();
throw new AdbException("ADB命令执行超时");
} finally {
executor.shutdownNow();
if (process.isAlive()) {
process.destroy();
}
}
} catch (IOException | InterruptedException | ExecutionException e) {
throw new AdbException("执行ADB命令失败: " + e.getMessage());
}
}
/**
* 移除端口转发
*/
public static void removeForward(int localPort) throws AdbException {
executeAdbCommand("forward", "--remove", "tcp:" + localPort);
}
/**
* 检查设备连接状态
*
*/
public static boolean isDeviceConnected() {
try {
String output = executeAdbCommand("devices");
return output != null && output.contains("\tdevice");
} catch (AdbException e) {
return false;
}
}
public static class AdbException extends Exception {
public AdbException(String message) {
super(message);
}
}
Android服务端代码
private String TAG = TcpServerHelper.class.getSimpleName();
public static boolean tcpIsSendSuccess = false;
public static boolean available = true;
public static int dataAreaLength = 0;
private int port;//端口
private long lastReConnectTime = 0;//上一次重连时间
private ConcurrentHashMap<String, ClientHandler> connectedClients;
private boolean isRunning;
private ServerSocket serverSocket;
public TcpServerHelper(int port) {
this.port = port;
tcpConnect(port);
}
public void tcpConnect(int port) {
this.port = port;
connectedClients = new ConcurrentHashMap<>();
start();
}
public void reConnect() {
if ((System.currentTimeMillis() - lastReConnectTime) > 1000 * 5 && !tcpIsSendSuccess) {//上一次重连间隔应大于10秒
UIUtils.runOnUIThread(new Runnable() {
@Override
public void run() {
PhoneUtil.wakeUpAndUnlock();
}
});
try {
stop();
Thread.sleep(2000);
start();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void closeConnect() {
LogUtils.e(TAG, "关闭连接");
stop();
}
public boolean sendData(byte[] data) {
ThreadManager.getThreadPool().execute(new Runnable() {
@Override
public void run() {
try {
for (ClientHandler client : connectedClients.values()) {
// 这里可以根据需求向特定设备发送数据
if (client.isConnected()) {//客户端是连接状态
client.sendMessage(data);
}
}
} catch (Exception e) {
LogUtils.e("发送失败" + e.getMessage());
tcpIsSendSuccess = false;
reConnect();
e.printStackTrace();
}
}
});
return true;
}
public void start() {
isRunning = true;
ThreadManager.getThreadPool().execute(new Runnable() {
@Override
public void run() {
try {
serverSocket = new ServerSocket(port);
Log.d(TAG, "Server started on port " + port);
while (isRunning) {
Socket clientSocket = serverSocket.accept();
String clientId = clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort();
Log.d(TAG, "New client connected: " + clientId);
ClientHandler clientHandler = new ClientHandler(clientSocket, clientId);
connectedClients.put(clientId, clientHandler);
ThreadManager.getThreadPool().execute(clientHandler);
}
} catch (IOException e) {
if (isRunning) {
Log.d(TAG, "Server error: " + e.getMessage());
}
}
}
});
}
public void stop() {
isRunning = false;
try {
if (null != connectedClients) {
// 关闭所有客户端连接
for (ClientHandler client : connectedClients.values()) {
client.close();
}
connectedClients.clear();
}
if (null != serverSocket && !serverSocket.isClosed()) {
serverSocket.close();
}
Log.d(TAG, "Server stopped");
} catch (IOException e) {
Log.d(TAG, "Error stopping server: " + e.getMessage());
}
}
/**
* 客户端管理线程
*/
private class ClientHandler implements Runnable {
private final Socket socket;
private final String clientId;
private boolean connected;
private InputStream inputStream;
public ClientHandler(Socket socket, String clientId) {
this.socket = socket;
this.clientId = clientId;
this.connected = true;
}
public boolean isConnected() {
return connected;
}
public void sendMessage(byte[] data) {
try {
if (connected) {
socket.getOutputStream().write(data);
socket.getOutputStream().flush();
}
} catch (IOException e) {
Log.e(TAG, "Error sending to " + clientId + ": " + e.getMessage());
close();
}
}
@Override
public void run() {
try {
while (connected) {
LogUtils.e("开启tcp线程");
if (null == inputStream) {
inputStream = socket.getInputStream();
}
int testCount = 0;
//TODO 读取数据业务逻辑
}
} catch (IOException e) {
Log.e(TAG, "Client " + clientId + " error: " + e.getMessage());
} finally {
if (null != inputStream) {
try {
inputStream.close();
inputStream = null;
} catch (IOException ignored) {
}
}
close();
}
}
public void close() {
connected = false;
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
connectedClients.remove(clientId);
Log.d(TAG, "Client disconnected: " + clientId);
} catch (IOException e) {
Log.e(TAG, "Error closing client " + clientId + ": " + e.getMessage());
}
}
}