安卓Java Web Socket简单使用
android使用javaWebSocket实现跨设备设备通讯的方式之一。
demo地址:https://gitee.com/sixu_Java/java-web-socket
只要关注onOpen 、onMessage 、onClose 、onError 这四个方法,然后server端启动,client端通过server端ip与端口去连接。即可完成简单的连接demo。
而server端与client端的通讯关注onMessage 。
注意:操作界面需要在UI线程,如将接收到的message更新到页面上,可借助Handler。如果直接在onMessage直接更新页面,则不会走onMessage,会走onError
断线重连必定会走onClose 方法,所以根据client的onClose方法断线的原因,调用reconnect 或者reconnectBlocking 即可完成重连动作。
准备
-
首先需要创建两个项目,作为通讯的serve端与client端。 -
引入Java Web Socket依赖 implementation "org.java-websocket:Java-WebSocket:1.4.0"
Java-WebSocket的github地址https://github.com/TooTallNate/Java-WebSocket
-
给client端和serve端在AndroidManifest.xml添加联网权限 <uses-permission android:name="android.permission.INTERNET" />
Client端
继承WebSocketClient
新建类继承WebSocketClient ,并实现4个方法。我们所有关于通讯的操作基本上都围绕着这个类。
@Override
public void onOpen(ServerHandshake handshakedata) {
}
@Override
public void onMessage(String message) {
}
@Override
public void onMessage(ByteBuffer bytes) {
}
@Override
public void onClose(int code, String reason, boolean remote) {
}
@Override
public void onError(Exception ex) {
}
-
onOpen:当连接上serve端时调用。 -
onMessage:接收到消息时调用。 -
onClose:连接断开时调用。code 表示断开的原因,remote 表示断开是否是由于serve的原因。 code可在org.java_websocket.framing.CloseFrame 中查看类型,根据code可以决定是否需要进行重连。
如code=1,则表示client与server从来没有连接过,则不需要重连。
code = 1000(Normal),表示是正常断开,一般情况下已经完成了要做的事情,用户手动断开,无需重连。
code= 1001 (GOING_AWAY),服务器可能由于自身原因导致断开,可重连。
-
onError:连接出错时调用,之后会调用onClose。
代码:
public class JWSClient extends WebSocketClient {
private static final String TAG = "JWSClient";
private JWSCallBack mCallBack;
public JWSClient(URI serverUri, JWSCallBack callBack) {
this(serverUri);
mCallBack = callBack;
}
private JWSClient(URI serverUri) {
super(serverUri);
}
@Override
public void onOpen(ServerHandshake handshakedata) {
Log.d(TAG, "onOpen: ");
mCallBack.onOpen(handshakedata);
}
@Override
public void onMessage(String message) {
Log.d(TAG, "onMessage: ");
mCallBack.onMessage(message);
}
@Override
public void onMessage(ByteBuffer bytes) {
super.onMessage(bytes);
Log.d(TAG, "onMessage: ");
mCallBack.onMessage(bytes);
}
@Override
public void onClose(int code, String reason, boolean remote) {
Log.d(TAG, "onClose: code = " + code + ", reason = " + reason + ", remote = " + remote);
if (code==-1){
return;
}
new Thread(new Runnable() {
@Override
public void run() {
try {
reconnectBlocking();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
}).start();
}
@Override
public void onError(Exception ex) {
Log.d(TAG, "onError: " + ex.getMessage());
}
interface JWSCallBack {
void onOpen(ServerHandshake handshakedata);
void onMessage(String message);
void onMessage(ByteBuffer bytes);
}
}
连接
以ws://ip地址:端口 生成一个WebSocketClient 对象,然后调用connect方法,即尝试连接serve。
ip地址表示serve的ip地址,端口需要与serve约定,保持一致。
以ws开头表示未加密,wws开头表示加密。
connect方法不会阻塞,connectBlocking会阻塞当前线程。
连接成功后,调用WebSocketClient的send方法,即可向serve发送数据。
send的参数是byte[]类型,则serve的onMessage收到的也是byte[]。
send的参数是string类型,则serve的onMessage收到的也是string。
代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTextViewReceive;
private EditText mEditTextIP;
private EditText mEditTextSend;
private Button mButtonConnect;
private Button mButtonSend;
private JWSClient mJWSClient;
private JWSClient.JWSCallBack mJWSCallBack = new JWSClient.JWSCallBack() {
@Override
public void onOpen(ServerHandshake handshakedata) {
}
@Override
public void onMessage(String message) {
Message msg = Message.obtain();
msg.what = 0;
msg.obj = message;
mUIHandler.sendMessage(msg);
}
@Override
public void onMessage(ByteBuffer bytes) {
Message msg = Message.obtain();
msg.what = 0;
msg.obj = getString(bytes);
mUIHandler.sendMessage(msg);
}
};
private Handler mUIHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
mTextViewReceive.setText((String) msg.obj);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mEditTextIP = findViewById(R.id.edit_ip);
mEditTextSend = findViewById(R.id.edit_send);
mButtonConnect = findViewById(R.id.btn_connect);
mButtonSend = findViewById(R.id.btn_send);
mTextViewReceive = findViewById(R.id.text_receive);
}
public void connectServe(View view) {
String ip = mEditTextIP.getText().toString();
if (ip != null && ip.startsWith("192.168.")) {
try {
mJWSClient = new JWSClient(new URI("ws://" + ip + ":13333"), mJWSCallBack);
mJWSClient.setConnectionLostTimeout(5);
mJWSClient.connectBlocking(3, TimeUnit.SECONDS);
} catch (URISyntaxException | InterruptedException exception) {
exception.printStackTrace();
Toast.makeText(MainActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(MainActivity.this, "请输入正确IP", Toast.LENGTH_SHORT).show();
}
}
public void send(View view) {
String msg = mEditTextSend.getText().toString();
if (msg != null && !"".equals(msg)) {
if (mJWSClient != null && mJWSClient.isOpen()) {
Log.d(TAG, "send: ");
mJWSClient.send(msg);
}
} else {
Toast.makeText(MainActivity.this, "发送不能为空", Toast.LENGTH_SHORT).show();
}
}
public static String getString(ByteBuffer buffer) {
Charset charset = null;
CharsetDecoder decoder = null;
CharBuffer charBuffer = null;
try {
charset = Charset.forName("UTF-8");
decoder = charset.newDecoder();
charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
return charBuffer.toString();
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mJWSClient != null) {
mJWSClient.close();
}
}
}
serve端
继承WebSocketServer
与client差不多,只不过会多一个WebSocket 参数,每当有一个client去连接serve端时,就会生成一个WebSocket对象,可以获取client的ip地址。
onClose时,无法通过WebSocket获取地址。
service端启动
传入端口创建WebSocketServer对象,调用start方法即可。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTextViewReceive;
private TextView mTextConnectClient;
private JWSServe mJWSServe;
private List<String> mClientIpList = new ArrayList<>();
private JWSServe.JWSCallBack mJWSCallBack = new JWSServe.JWSCallBack() {
@Override
public void onOpen(ClientHandshake handshake) {
}
@Override
public void onMessage(String message) {
Message msg = Message.obtain();
msg.what = 0;
msg.obj = message;
mUIHandler.sendMessage(msg);
}
@Override
public void onMessage(ByteBuffer bytes) {
Message msg = Message.obtain();
msg.what = 0;
msg.obj = getString(bytes);
mUIHandler.sendMessage(msg);
}
@Override
public void updateSocketClient(HashMap<WebSocket, String> socketHashMap) {
mClientIpList = new ArrayList<>(socketHashMap.values());
mUIHandler.sendEmptyMessage(1);
}
};
private Handler mUIHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
mTextViewReceive.setText((String) msg.obj);
break;
case 1:
StringBuffer stringBuffer = new StringBuffer();
if (mClientIpList.size() < 1) {
stringBuffer.append("当前无设备连接\n");
} else {
for (String ip : mClientIpList) {
stringBuffer.append(ip + ":已连接\n");
}
}
mTextConnectClient.setText(stringBuffer.toString());
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextViewReceive = findViewById(R.id.text_receive);
mTextConnectClient = findViewById(R.id.text_connect_client);
}
@Override
protected void onResume() {
super.onResume();
mJWSServe = new JWSServe(13333, mJWSCallBack);
mJWSServe.setConnectionLostTimeout(5);
mJWSServe.start();
}
public void send(View view) {
String msg = ((EditText) findViewById(R.id.edit_send)).getText().toString();
if (msg != null && !"".equals(msg)) {
mJWSServe.send(mClientIpList, msg);
} else {
Toast.makeText(MainActivity.this, "发送不能为空", Toast.LENGTH_SHORT).show();
}
}
public static String getString(ByteBuffer buffer) {
Charset charset = null;
CharsetDecoder decoder = null;
CharBuffer charBuffer = null;
try {
charset = Charset.forName("UTF-8");
decoder = charset.newDecoder();
charBuffer = decoder.decode(buffer.asReadOnlyBuffer());
return charBuffer.toString();
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}
@Override
protected void onPause() {
super.onPause();
try {
mJWSServe.stop();
} catch (IOException exception) {
exception.printStackTrace();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
JavaWebSocket的心跳机制
client和server的重连是依靠Ping 和Pong 。
如client调用ping方法,如果server收到消息会,会立刻给client回复一个Pong。则client的Pong方法会立刻被调用。
实际上实现使用JavaWebSocket并不需要如此复杂,在server端启动或client端连接前,通过setConnectionLostTimeout 设置一个连接超时的时间(未设置则默认时间一分钟,设置为0或负数表示关闭超时检测)。
serve会根据设置时间检测client列表有哪些已超时,则会走onClose。
client设置时间后,serve会每隔固定式见向client发送ping,如果client超时没有收到pong,则走onError和onClose。
|