以往开发过多款安卓嵌入式设备,这些设备与PC通讯主要通过设备上的以太网网口进行网络通讯,最近一个项目设备没有以太网网口,设备与PC通讯要求使用普通安卓数据线连接设备与PC完成数据通讯。
查阅相关资料以及对adb操作指令的了解,最终实现了项目需求。
方案实现思路:
1.设备端开启一个socket服务,服务端口12345;
2.设备端监听USB插拔事件来启动关闭设备端的socket服务;
3.PC端使用adb命令:adb forward tcp:54321 tcp:12345,将PC端口54321上发来的数据转换到设备socket服务端口;
4.PC端启动socket客户端与PC端口54321进行数据通讯。
因为项目代码保密,故对方案实现特意写了一个demo程序。
Demo实现了:
1.安卓设备端socket服务,服务监听端口12345,收到任何消息,均回复设备的毫秒时间戳。
2.PC 实现使用adb命令将PC端口54321消息转发到设备服务端口12345,之后,连接socket连接,并监听端口54321的消息,每隔3s发送PC毫秒时间戳,并监听设备端服务回应的数据。
以下以代码进行进行介绍。
Android端:
package com.yx.usb2pc;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.util.Log;
/**
* UsbConnectReceiver
* 主要监听ACTION_USB_STATE,启动关闭socket服务
*
* @author yx
* @date 2019/10/30 20:39
*/
public class UsbConnectReceiver extends BroadcastReceiver {
private static final String TAG = "UsbConnectReceiver";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(UsbManager.EXTRA_PERMISSION_GRANTED)) {
boolean permissionGranted =
intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
Log.v(TAG, "permissionGranted : " + permissionGranted);
}
String action = intent.getAction();
Log.v(TAG, "action:" + action);
switch (action) {
case UsbManager.ACTION_USB_ACCESSORY_ATTACHED:
case UsbManager.ACTION_USB_ACCESSORY_DETACHED:
UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
Log.v(TAG, accessory.toString());
break;
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
Log.v(TAG, UsbManager.ACTION_USB_DEVICE_ATTACHED);
break;
case UsbManager.ACTION_USB_DEVICE_DETACHED:
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Log.v(TAG, device.toString());
break;
case UsbManager.ACTION_USB_STATE:
boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
boolean functionAdb = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
Log.v(TAG, "connected:" + connected + " function_adb : " + functionAdb);
// 断开停止服务
if (!connected) {
SocketServer.getSocketServerInstant().stopServer();
} else {
// 开启adb调试功能,开启服务
if (functionAdb) {
SocketServer.getSocketServerInstant().startServer();
}
}
break;
default:
break;
}
}
}
UsbConnectReceiver,广播接收器,监听UsbManager.ACTION_USB_STATE状态变化,如果连接断开(connected==false),关闭设备端的socket服务,当然已连接上需要判断adb功能是否使能才能开启服务。
package com.yx.usb2pc;
import android.util.Log;
import com.yx.usb2pc.utils.ThreadPoolManager;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* SocketServer
* 服务端接收socket信息,回复设备端的时间戳信息
*
* @author yx
* @date 2019/10/30 20:39
*/
public class SocketServer {
private static final String TAG = "SocketServer";
private static final int PORT = 12345;
private ServerSocket mServerSocket = null;
private boolean mRunning = false;
private static SocketServer sSocketServer;
public static SocketServer getSocketServerInstant() {
if (sSocketServer == null) {
sSocketServer = new SocketServer();
}
return sSocketServer;
}
private SocketServer() {
}
/**
* 启动服务
*/
public void startServer() {
stopServer();
ThreadPoolManager.getInstance().startTaskThread(new ServerThread(), "server-thread");
}
/**
* 关闭服务
*/
public void stopServer() {
if (mServerSocket != null) {
mRunning = false;
try {
mServerSocket.close();
Log.d(TAG, "stopServer server ");
} catch (Exception e) {
e.printStackTrace();
}
mServerSocket = null;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ServerThread extends Thread {
@Override
public void run() {
mRunning = true;
try {
mServerSocket = new ServerSocket(PORT);
Log.d(TAG, "start server port = " + PORT);
while (mRunning) {
Socket socket = mServerSocket.accept();
Log.d(TAG, "accept ");
ThreadPoolManager.getInstance()
.startTaskThread(new ReceiveThread(socket), "receive-thread");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mServerSocket != null) {
try {
mServerSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
class ReceiveThread extends Thread {
private Socket socket;
public ReceiveThread(Socket socket) {
this.socket = socket;
Log.d(TAG, " socket:" + socket.toString());
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
while (true) {
String data = dis.readUTF();
Log.d(TAG, "receive:" + data);
// 接收到后回复设备的毫秒时间戳
String s = "device:" + System.currentTimeMillis();
dos.writeUTF(s);
dos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
SocketServer设备端socket服务,主要提供服务开启关闭方法,服务开启后ServerThread会循环侦听数据消息,每侦听到消息开启一个ReceiveThread线程处理数据,demo中数据接收到后回复设备的毫秒时间戳。
PC端:
package com.yx.soclectclient;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* SocketClient
*
* PC端每隔3s给监听端口发送PC时间戳信息
*
* @author yx
* @date 2019/5/17 13:57
*/
public class SocketClient {
private Socket mSocket = null;
private static final int PORT = 54321;
/**
* adb 命令绑定端口
*
* @return
*/
private boolean adbForward() {
String cmd = "adb forward tcp:54321 tcp:12345";
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
Process process = null;
try {
process = Runtime.getRuntime().exec(cmd);
process.waitFor();
return true;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (process != null) {
process.destroy();
}
}
return false;
}
private void test() {
try {
mSocket = new Socket("127.0.0.1", PORT);
System.out.println("socket:" + mSocket.toString());
DataInputStream dis = new DataInputStream(mSocket.getInputStream());
DataOutputStream dos = new DataOutputStream(mSocket.getOutputStream());
while (true) {
String data = "sendTime:" + System.currentTimeMillis();
dos.writeUTF(data);
dos.flush();
String s = dis.readUTF();
System.out.println("receive:" + s);
Thread.sleep(3000);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SocketClient client = new SocketClient();
if (client.adbForward()) {
client.test();
}
}
}
SocketClient为PC端测试类,主要完成adb命令完成端口绑定,建立socket客户端连接,连接指向端口54321。连接后,每隔3s向端口发送PC时间戳,并侦听端口数据恢复。
ps:
1.本demo代码,android sdk版本为25;
2.因为测试时连接相对稳定,故未考虑socket长连接心跳问题及断线重连问题,可靠性应用需考虑心跳及断线重连问题。
3.PC端需要按照adb环境,使用时android设备需要打开adb调试模式。
4.USB数据线模拟网线进行socket通讯关键点是利用adb端口绑定命令:adb forward tcp:[pc port] tcp:[device port]。
5.demo源码:https://github.com/TomcatXiong/UsbPcCommunicationDemo