Android设备分别作为客户端和服务端与PC通讯
背景简介
这是一项比较老旧的技术了,不过在一些特定的场合(例如:无网络),进行传输数据,使用这项技术还是很方便的。网上有好很多类似的文章,个人觉得都不是特别完整,正好公司最近有涉及这方面的功能需求,借此机会整理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中依赖包的版本,或直接屏蔽掉,不影响功能。
最后谢谢您能花时间来看这篇文章,希望对您有所帮助!