Android设备通过USB线连接PC进行Socket通信
Android设备分别作为客户端和服务端与PC通讯
- 背景简介
- 核心原理
- 整体思路
- 代码讲解
- 1.创建广播监听类ConnectStateReceiver
- 2.建立Socket连接
- 3.Socket连接成功,数据交互
- 4.断开连接,释放资源
- 彩蛋部分
- 1.FileTransferUtil.java
- 1.SendRequestUtil.java
- 完整Demo下载:
背景简介
这是一项比较老旧的技术了,不过在一些特定的场合(例如:无网络),进行传输数据,使用这项技术还是很方便的。网上有好很多类似的文章,个人觉得都不是特别完整,正好公司最近有涉及这方面的功能需求,借此机会整理Android分别作为客户端和服务端的实现。
核心原理
其实Socket通信,最根本的就是,Socket客户端和Socket服务端通过输入流和输出流进行数据传输。
整体思路
1.通过广播,来监听USB状态及Socket状态。
2.建立Socket连接。
3.获取Socket对象中的输入流或输出流,实现数据的接收或发送。
4.释放资源,关闭连接。
代码讲解
使用Rxjava和线程池来进行异步处理,发挥Service的优势增加稳定性。虽然是个小的功能,不过我也进行了多层封装,是代码更加健壮,灵活。
1.创建广播监听类ConnectStateReceiver
根据不同场景,分别进行说明
//服务端在监听private static final String ACTION_SERVER_LISTENING = "SocketServerListening";//Socket连接成功private static final String ACTION_CONNECT_SUCCESS = "SocketConnectSuccess";//Socket连接失败private static final String ACTION_CONNECT_FAIL = "SocketConnectFail";//USB线连接状态private static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
场景一,Android作为客户端,PC作为服务端:(要求Android设备与PC所处同一局域网内)
1.ACTION_USB_STATE :USB连接状态,系统自行发送。
2.ACTION_SERVER_LISTENING :PC的Socket服务端正在监听,PC发送。
3.ACTION_CONNECT_SUCCESS :Socket连接成功,PC发送。
4.ACTION_CONNECT_FAIL :Socket连接失败,PC发送。
场景二,Android作为服务端,PC作为客户端:
1.ACTION_USB_STATE :USB连接状态,系统自行发送。
2.ACTION_CONNECT_SUCCESS :Socket连接成功,Android发送。
3.ACTION_CONNECT_FAIL :Socket连接失败,Android发送。
如何处理接收到的通知:
String action = intent.getAction(); if (TextUtils.equals(action, ACTION_SERVER_LISTENING)) { ToastUtil.showToast(context, "服务端开始监听"); LogUtil.e("服务端开始监听"); //连接服务端 SocketManager.getInstance().connectServer(context); } else if (TextUtils.equals(action, ACTION_CONNECT_SUCCESS)) { ToastUtil.showToast(context, "Socket连接成功"); LogUtil.e("Socket连接成功"); //1.初始化发送请求工具类 SendRequestUtil.getInstance().init(SocketManager.getInstance().getDataOutputStream()); //2.启动Service,接收消息 context.startService(new Intent(context, DataService.class)); } else if (TextUtils.equals(action, ACTION_CONNECT_FAIL)) { ToastUtil.showToast(context, "Socket连接失败"); LogUtil.e("Socket连接失败"); } else if (TextUtils.equals(action, ACTION_USB_STATE)) { boolean connectedState = intent.getBooleanExtra("connected", false); if (connectedState) { ToastUtil.showToast(context, "USB已连接,开始监听客户端"); LogUtil.e("USB已连接,开始监听客户端"); //开始监听 SocketManager.getInstance().acceptClient(context); } else { ToastUtil.showToast(context, "USB断开连接"); LogUtil.e("USB断开连接"); //1.关闭Service,停止接收消息 context.stopService(new Intent(context, DataService.class)); //2.释放socket SocketManager.getInstance().close(); } }
以上代码结合了两种场景,可以根据自己的需求,对代码进行适当删减。
2.建立Socket连接
根据不同场景,分别进行说明
场景一,Android作为客户端,PC作为服务端:(要求Android设备与PC所处同一局域网内)
收到PC端发来的“Socket服务端正在监听”通知(即ACTION_SERVER_LISTENING)后,进行连接操作:
public void connectServer(final Context context) { mConnectDisposable = Observable.create(new ObservableOnSubscribe<Boolean>() { @Override public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception { Socket socket = connect(); if (socket != null) { emitter.onNext(true); } else { emitter.onNext(false); } emitter.onComplete(); } }).subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean aBoolean) throws Exception { if (aBoolean) { ToastUtil.showToast(context, "连接服务端成功"); mState = STATE_CONNECT_SUCCESSED; } else { ToastUtil.showToast(context, "连接服务端失败"); mState = STATE_CONNECT_FAILED; //弹出Dialog,提示重新连接 } } }); } private Socket connect() { Socket socket = null; try { socket = new Socket(AppConfig.SERVER_ADDRESS, AppConfig.SERVER_PORT); } catch (IOException e) { e.printStackTrace(); } if (socket != null) { mSocket = socket; try { mDataInputStream = new DataInputStream(mSocket.getInputStream()); mDataOutputStream = new DataOutputStream(mSocket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } return socket; }
场景二,Android作为服务端,PC作为客户端:
当收到系统发送的“USB已连接状态”通知(即ACTION_USB_STATE),后进行Socket监听,等待PC接入:
public void acceptClient(final Context context) { if(mState != STATE_LISTEN){ mState = STATE_LISTEN; mAcceptDisposable = Observable.create(new ObservableOnSubscribe<Boolean>() { @Override public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception { Socket socket = accept(); if (socket != null) { emitter.onNext(true); } else { emitter.onNext(false); } emitter.onComplete(); } }).subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean aBoolean) throws Exception { if (aBoolean) { ToastUtil.showToast(context, "接入客户端成功"); mState = STATE_CONNECT_SUCCESSED; context.sendBroadcast(new Intent(ConnectStateReceiver.ACTION_CONNECT_SUCCESS)); } else { ToastUtil.showToast(context, "接入客户端失败"); mState = STATE_CONNECT_FAILED; context.sendBroadcast(new Intent(ConnectStateReceiver.ACTION_CONNECT_FAIL)); } } }); } } private Socket accept() { Socket socket = null; try { if(mServerSocket == null){ mServerSocket = new ServerSocket(AppConfig.LOCAL_PORT); } socket = mServerSocket.accept(); } catch (IOException e) { e.printStackTrace(); } if (socket != null) { mSocket = socket; try { mDataInputStream = new DataInputStream(mSocket.getInputStream()); mDataOutputStream = new DataOutputStream(mSocket.getOutputStream()); mServerSocket.close(); } catch (IOException e) { e.printStackTrace(); } } return socket; }
3.Socket连接成功,数据交互
Socket成功建立连接,收到“Socket连接成功”通知(即ACTION_CONNECT_SUCCESS),我们就可以通过操作Socket提供的输入流和输出流进行数据交互。
Socket为双向通信,既可以接收数据,也可以发送数据。
接收数据
原理:读取Socket中的输入流,如果需要下载文件,则配合本地的输出流,就可以进行消息或文件的接收。
在后台启动一个Service,用于接收数据,直到USB断开连接。
context.startService(new Intent(context, DataService.class));
以下为Service代码以及接收数据线程的代码 (FileTransferUtil是封装的工具类,用于文件的操作):
public class DataService extends Service { private ExecutorService mExecutorService; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); mExecutorService = Executors.newCachedThreadPool(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { ReceiveRunnable receiveRunnable = new ReceiveRunnable(SocketManager.getInstance().getDataInputStream()); mExecutorService.execute(receiveRunnable); return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); mExecutorService.shutdown(); }}public class ReceiveRunnable implements Runnable { private DataInputStream mDataInputStream; public ReceiveRunnable(DataInputStream dis) { mDataInputStream = dis; } @Override public void run() { try { while (true) { String jsonRaw = mDataInputStream.readUTF(); DataEvent dataEvent = new DataEvent("testJSON"); dataEvent.setTestJSON(jsonRaw); EventManager.post(dataEvent); FileTransferUtil.getInstance().downloadFile(mDataInputStream, fileLength); } } catch (IOException e) { e.printStackTrace(); } }}
发送数据
原理:操作Socket中的输出流,如果需要上传文件,则配合本地的输入流,就可以进行消息或文件的发送。
初始化SendRequestUtil工具类
SendRequestUtil.getInstance().init(SocketManager.getInstance().getDataOutputStream());
(SendRequestUtil是封装的工具类,用于发送消息及文件操作,可随时在点击事件里面进行调用)
以下为SendRequestUtil中的核心代码:
public Disposable post(final String jsonString, final File localFile) { return Observable.create(new ObservableOnSubscribe<Boolean>() { @Override public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception { if (localFile == null) { emitter.onNext(sendInfor(jsonString)); } else { emitter.onNext(sendInforAndFile(jsonString, localFile)); } emitter.onComplete(); } }).subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean aBoolean) throws Exception { if(mRequestListener == null){ return; } if(aBoolean){ mRequestListener.onRequestSuccessed(); }else{ mRequestListener.onRequestFailed(); } } }); } private boolean sendInfor(String json) { try { mDataOutputStream.writeUTF(json); mDataOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); try { mDataOutputStream.flush(); } catch (IOException e1) { e1.printStackTrace(); } return false; } return true; } private boolean sendInforAndFile(String json, File file) { boolean result = false; try { mDataOutputStream.writeUTF(json); result = FileTransferUtil.getInstance().uploadFile(mDataOutputStream, file); } catch (IOException e) { e.printStackTrace(); } return result; }
4.断开连接,释放资源
在我们数据交互结束后,一定要记得释放资源。
通过监听USB的连接状态,可以在USB线断开连接的时候,收到“USB连接状态”通知(即ACTION_USB_STATE),进行资源的释放。
1.停止Service,不再接收数据。
context.stopService(new Intent(context, DataService.class));
2.释放socket,关闭文件流
SocketManager.getInstance().close();
看一下close()方法中的代码:
public void close() { if (mSocket != null) { mState = STATE_NONE; try { mDataInputStream.close(); mDataOutputStream.close(); mSocket.close(); mSocket = null; } catch (IOException e) { e.printStackTrace(); } } }
彩蛋部分
1.FileTransferUtil.java
public class FileTransferUtil { private static volatile FileTransferUtil mInstance = null; private Timer mTimer; private TimerTask mTimerTask = null; private String mProgress = ""; private FileTransferUtil() { mTimer = new Timer(); } public static FileTransferUtil getInstance() { if (mInstance == null) { synchronized (FileTransferUtil.class) { if (mInstance == null) { mInstance = new FileTransferUtil(); } } } return mInstance; } public void downloadFile(DataInputStream dataInputStream, long fileLength) { FileOutputStream fos = null; try { fos = new FileOutputStream(AppConfig.SAVE_PATH + AppConfig.DB_FILE_NAME); byte[] b = new byte[4 * 1024]; int length; long writeLength = 0L; while ((length = dataInputStream.read(b)) != -1) { fos.write(b, 0, length); writeLength += length; int progress = (int) (((float) writeLength / (float) fileLength) * 100); if (writeLength >= fileLength) { transferCompleted(); break; } updateProgress(progress + "%"); } } catch (IOException ioe) { ioe.printStackTrace(); transferFailed(); } finally { try { if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } public boolean uploadFile(DataOutputStream dataOutputStream, File file) { FileInputStream fis = null; try { long fileLength = file.length(); fis = new FileInputStream(file); int length; long readLength = 0L; byte[] b = new byte[4 * 1024]; while ((length = fis.read(b)) != -1) { dataOutputStream.write(b, 0, length); readLength += (long) length; int progress = (int) (((float) readLength / (float) fileLength) * 100); if (readLength >= fileLength) { transferCompleted(); break; } updateProgress(progress + "%"); } return true; } catch (IOException ioe) { ioe.printStackTrace(); transferFailed(); return false; } finally { try { if (fis != null) { fis.close(); } dataOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } } private void updateProgress(String progress) { mProgress = progress; if (mTimerTask == null) { mTimerTask = new TimerTask() { @Override public void run() { DataEvent dataEvent = new DataEvent(FileConstant.FILE_PROGRESS); dataEvent.setProgress(mProgress); EventManager.post(dataEvent); } }; mTimer.schedule(mTimerTask, 1000); } } private void transferCompleted() { mTimer.cancel(); mTimerTask = null; DataEvent dataEvent = new DataEvent(FileConstant.FILE_TRANSFER_COMPLETE); dataEvent.setProgress("100%"); EventManager.post(dataEvent); } private void transferFailed() { mTimer.cancel(); mTimerTask = null; DataEvent dataEvent = new DataEvent(FileConstant.FILE_TRANSFER_FAILED); EventManager.post(dataEvent); }}
1.SendRequestUtil.java
public class SendRequestUtil { private DataOutputStream mDataOutputStream; private SendRequestUtil() { } private static class Holder { private static final SendRequestUtil INSTANCE = new SendRequestUtil(); } public static SendRequestUtil getInstance() { return Holder.INSTANCE; } public void init(DataOutputStream dos) { mDataOutputStream = dos; } public Disposable post(final String jsonString, final File localFile) { return Observable.create(new ObservableOnSubscribe<Boolean>() { @Override public void subscribe(ObservableEmitter<Boolean> emitter) throws Exception { if (localFile == null) { emitter.onNext(sendInfor(jsonString)); } else { emitter.onNext(sendInforAndFile(jsonString, localFile)); } emitter.onComplete(); } }).subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean aBoolean) throws Exception { if(mRequestListener == null){ return; } if(aBoolean){ mRequestListener.onRequestSuccessed(); }else{ mRequestListener.onRequestFailed(); } } }); } private boolean sendInfor(String json) { try { mDataOutputStream.writeUTF(json); mDataOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); try { mDataOutputStream.flush(); } catch (IOException e1) { e1.printStackTrace(); } return false; } return true; } private boolean sendInforAndFile(String json, File file) { boolean result = false; try { mDataOutputStream.writeUTF(json); result = FileTransferUtil.getInstance().uploadFile(mDataOutputStream, file); } catch (IOException e) { e.printStackTrace(); } return result; } private OnRequestListener mRequestListener; public void registerRequestListener(OnRequestListener listener){ this.mRequestListener = listener; } public void unregisterRequestListener(){ this.mRequestListener = null; } public interface OnRequestListener{ void onRequestSuccessed(); void onRequestFailed(); }}
完整Demo下载:
USBSocketClient.
说明:
我的开发环境有点旧,是Android Studio3.2,下载源码如果出现build失败,请更换build.gradle中依赖包的版本,或直接屏蔽掉,不影响功能。
最后谢谢您能花时间来看这篇文章,希望对您有所帮助!
更多相关文章
- Android(安卓)MediaPlayer 常用方法介绍
- Android的网络状态判断
- 【安卓笔记】android客户端与服务端交互的三种方式
- Android打开/关闭数据流量
- 重定向android log
- TabHost页卡
- Android(安卓)MediaPlayer 常用方法介绍
- Android电量和插拔电源状态广播监听
- Android(安卓)SDK自带教程之BluetoothChat