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中依赖包的版本,或直接屏蔽掉,不影响功能。

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

更多相关文章

  1. Android(安卓)MediaPlayer 常用方法介绍
  2. Android的网络状态判断
  3. 【安卓笔记】android客户端与服务端交互的三种方式
  4. Android打开/关闭数据流量
  5. 重定向android log
  6. TabHost页卡
  7. Android(安卓)MediaPlayer 常用方法介绍
  8. Android电量和插拔电源状态广播监听
  9. Android(安卓)SDK自带教程之BluetoothChat

随机推荐

  1. EditView属性大全
  2. Android异步处理三:Handler+Looper+Messag
  3. Android(安卓)SDK Android(安卓)NDK Andr
  4. android中ListView拖动时背景黑色的问题
  5. android 环境搭建
  6. Android的SharedPreferences和Preference
  7. Android文件的读写
  8. Android随笔之布局属性简单用法
  9. Android.Essentials[精要]
  10. Android(安卓)Launcher3主菜单背景改为黑