Android设备通过USB线连接PC进行Socket通信

背景简介

这是一项比较老旧的技术了,不过在一些特定的场合(例如:无网络),进行传输数据,使用这项技术还是很方便的。网上有好很多类似的文章,个人觉得都不是特别完整,正好公司最近有涉及这方面的功能需求,借此机会整理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中依赖包的版本,或直接屏蔽掉,不影响功能。

最后谢谢您能花时间来看这篇文章,希望对您有所帮助!

猜你喜欢

转载自blog.csdn.net/hedgehog_ming/article/details/107755043