2. WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
下面主要使用“JavaWebSocket”开源项目,实现Android端与服务器端消息互通。
参考下文献:http://blog.csdn.net/tangxl2008008/article/details/52421413
http://blog.csdn.net/li352558693/article/details/42639683
http://www.cnblogs.com/lliuzl/articles/4550493.html
http://www.open-open.com/lib/view/open1476778263175.html
先看下我在项目中使用的WebSocket 通信,再介绍:
* 聊天页面
*/
public class ChatActivity extends BaseActivity {
private ImageView iv_back;
private TextView tv_title;
private RecyclerView recycleview;
private ImageView iv_add;
private EditText et_text;
private ImageView iv_send;
private List datas;
private ChatListAdapter chatListAdapter;
private String content = "";
private ChatHistoryBean chatBean;
private WebSocketClient webSocketClient;
private ChatHistoryBean.TalkDataListBean myTalkBean;
private ChatHistoryBean.TalkDataListBean myTalkBean_img;//上传图片的bean
private Date date_time;
private SimpleDateFormat sdf;
private String link_user_id;
private String link_user_name;
private String link_user_image;
private Boolean isMailChat;
private RelativeLayout rl_titlebar;
@Override
public int getLayoutResId() {
datas = new ArrayList<>();
isMailChat = getIntent().getBooleanExtra("isMailChat", false);
return R.layout.activity_chat;
}
@Override
protected void initView() {
iv_back = (ImageView) findViewById(R.id.iv_back);
iv_back.setVisibility(View.VISIBLE);
rl_titlebar = (RelativeLayout) findViewById(R.id.rl_titlebar);
tv_title = (TextView) findViewById(R.id.tv_title);
iv_add = (ImageView) findViewById(R.id.iv_add);
et_text = (EditText) findViewById(R.id.et_text);
iv_send = (ImageView) findViewById(R.id.iv_send);
recycleview = (RecyclerView) findViewById(R.id.recycleview);
recycleview.setLayoutManager(new LinearLayoutManager(activity, LinearLayout.VERTICAL, false));
date_time = new Date();
sdf = new SimpleDateFormat("HH:mm:ss");
}
@Override
protected void initListener() {
iv_back.setOnClickListener(this);
iv_add.setOnClickListener(this);
iv_send.setOnClickListener(this);
}
@Override
protected void initData() {
// tv_title.setText("连接中...");
if (isMailChat) {
rl_titlebar.setBackgroundColor(getResources().getColor(R.color.titlebar_bg_blue));
} else {
rl_titlebar.setBackgroundColor(getResources().getColor(R.color.titlebar_bg));
}
chatListAdapter = new ChatListAdapter(activity, datas);//聊天列表
initWebSocketClient();//初始化webSocket
chatBean = (ChatHistoryBean) getIntent().getSerializableExtra("bean");
if (chatBean == null) {
//如果为null,表示不是从聊天历史记录中跳转过来的,而是从其他页面点击"联系我们"跳转进来的
link_user_id = getIntent().getStringExtra("link_id");
link_user_name = getIntent().getStringExtra("link_user_name");
link_user_image = getIntent().getStringExtra("link_user_image");
//获取单个人的聊天历史记录
onLoadToGetSomeBodyChatHistory(link_user_id);
} else {
link_user_id = chatBean.link_user_id;
link_user_name = chatBean.link_user_name;
link_user_image = chatBean.link_user_image;
datas = chatBean.talkDataList;
chatListAdapter = new ChatListAdapter(activity, datas);
recycleview.setAdapter(chatListAdapter);
if (datas.size() != 0) {
recycleview.scrollToPosition(datas.size() - 1);
}
}
}
@Override
public void onClick(View view) {
super.onClick(view);
switch (view.getId()) {
case R.id.iv_back:
finish();
break;
case R.id.iv_add:
//添加图片
Intent intent = new Intent(this, SelectPicActivity.class);
startActivityForResult(intent, 101);
break;
case R.id.iv_send:
//发送文字
sendMessage();
break;
}
}
/**
* 初始化websocket
*/
private void initWebSocketClient() {
URI uri = null;
try {
if (isMailChat) {//员工聊天websocket的连接地址
uri = new URI(NetUtils.WS_CHAT_MAIL + SpUtils.getUserId(activity));
} else {//业务聊天websocket的连接地址
uri = new URI(NetUtils.WS_CHAT_BUSINESS + SpUtils.getUserId(activity));
}
} catch (Exception e) {
e.printStackTrace();
}
//连接中使用的new Draft_17()就是使用的协议version 17(RFC 6455),Tomcat 7.0使用的协议版本为RFC 6455。
Draft_17 draft = new Draft_17();
webSocketClient = new WebSocketClient(uri, draft) {
@Override//连接成功
public void onOpen(ServerHandshake handshakedata) {
Log.d(TAG, "run() returned: " + "连接到服务器");
String title = getIntent().getStringExtra("title");
changeTitle(title);
}
@Override//从服务端消息(聊天时对方发过来的消息)
public void onMessage(final String message) {
Log.d(TAG, "run() returned: " + message);
// 8735$$$$$$$$nature$$$$$$$$a0/51/97/a051979545fa49f18444fc7b2d8f6477.png$$$$$$$$8678$$$$$$$$anan$$$$$$$$db/cd/97/dbcd979f580049ae8cb6f0cbf5969f70.png$$$$$$$$0$$$$$$$$Zzzzz$$$$$$$$empty
// 字符串$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$字符串 用$$$$$$$$分割
// /****自己的id**自己的昵称**自己的头像***对方id***对放昵称**对方头像****消息类型(自己还是对方)****消息字符串****图片****/
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
String messages[] = message.split("\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024");//$$$$$$$$分割
myTalkBean = new ChatHistoryBean.TalkDataListBean();
myTalkBean.user_id = messages[0];
myTalkBean.user_name = messages[1];
myTalkBean.user_image = messages[2];
myTalkBean.link_user_id = messages[3];
myTalkBean.link_user_name = messages[4];
myTalkBean.link_user_image = messages[5];
myTalkBean.type = "1";
myTalkBean.text = messages[7];
myTalkBean.image = messages[8];
myTalkBean.time = sdf.format(date_time);
datas.add(myTalkBean);
chatListAdapter.notifyDataSetChanged();
recycleview.smoothScrollToPosition(datas.size() - 1);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
@Override//连接断开,remote判定是客户端断开还是服务端断开
public void onClose(int code, String reason, boolean remote) {
changeTitle("未连接");
Log.d(TAG, "onClose() returned: " + reason);
}
@Override
public void onError(Exception ex) {
Log.d(TAG, "onError() returned: " + ex);
changeTitle("连接异常");
}
};
webSocketClient.connect();
}
/**
* 发送文本消息
*/
private void sendMessage() {
// 字符串$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$字符串 用$$$$$$$$分割
// /****自己的id**自己的昵称**自己的头像***对方id***对放昵称**对方头像****消息类型(自己还是对方)****消息字符串****图片****/
String img;
content = et_text.getEditableText().toString();
if (StringUtils.isEmpty(content)) {
content = "empty";
return;
} else {
img = "empty";
}
runOnUiThread(new Runnable() {
@Override
public void run() {//webSocketClient发送的文字消息,不是图片
String con = SpUtils.getUserId(activity) + "$$$$$$$$" //自己的id
+ SpUtils.getUserName(activity) + "$$$$$$$$" //自己的昵称
+ SpUtils.getHeadimg(activity).split("files/")[1] + "$$$$$$$$" //自己的头像
+ link_user_id + "$$$$$$$$" //对方id
+ link_user_name + "$$$$$$$$" //对放昵称
+ link_user_image + "$$$$$$$$" //对方头像
+ "0" + "$$$$$$$$" //消息类型(自己还是对方)
+ content + "$$$$$$$$" //消息字符串 msg
+ "empty"; //图片服务器地址 且不带http前缀
et_text.setText("");
webSocketClient.send(con);
//刷新chatListAdapter
myTalkBean = new ChatHistoryBean.TalkDataListBean();
myTalkBean.time = sdf.format(date_time);
myTalkBean.user_id = SpUtils.getUserId(activity);
myTalkBean.user_name = SpUtils.getUserName(activity);
myTalkBean.user_image = SpUtils.getHeadimg(activity).split("files/")[1];
myTalkBean.link_user_id = link_user_id;
myTalkBean.link_user_name = link_user_name;
myTalkBean.link_user_image = link_user_image;
myTalkBean.type = "0";
myTalkBean.text = content;
myTalkBean.image = "empty";
datas.add(myTalkBean);
chatListAdapter.notifyDataSetChanged();
recycleview.scrollToPosition(datas.size() - 1);
}
});
}
/**
* 改变标题栏
*
* @param title
*/
private void changeTitle(final String title) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_title.setText(title);
}
});
}
@Override
protected void onDestroy() {
webSocketClient.close();
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 101 && resultCode == RESULT_OK) {
// 从相册返回的数据
String img_path = data.getStringExtra(SelectPicActivity.KEY_PHOTO_PATH);
Log.i(TAG, "最终选择的图片1=" + img_path);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(img_path, opts);
//先显示错误图片 ,图片上传完之后再显示正确图片
myTalkBean_img = new ChatHistoryBean.TalkDataListBean();
myTalkBean_img.time = sdf.format(date_time);
myTalkBean_img.user_id = SpUtils.getUserId(activity);
myTalkBean_img.user_name = SpUtils.getUserName(activity);
myTalkBean_img.user_image = SpUtils.getHeadimg(activity).split("files/")[1];
myTalkBean_img.link_user_id = link_user_id;
myTalkBean_img.link_user_name = link_user_name;
myTalkBean_img.link_user_image = link_user_image;
myTalkBean_img.type = "0";
myTalkBean_img.text = "empty";
myTalkBean_img.image = "img";
datas.add(myTalkBean_img);
chatListAdapter.notifyDataSetChanged();
recycleview.scrollToPosition(datas.size() - 1);
UploadFilesUtil.upLoadFile(img_path, new MyString2Callback() {
@Override
public void onError(Call call, Exception e) {
ToastUtils.showToast("上传失败");
}
@Override
public void onResponse(Call call, String s) {
// waitDialog.dismiss();
UploadResultBean resultBean = new Gson().fromJson(s, UploadResultBean.class);
if (resultBean.code == 0) {
//上传成功
String imgHttpPath = resultBean.message;//没有前缀http的服务器图片地址
String con = SpUtils.getUserId(activity) + "$$$$$$$$" //自己的id
+ SpUtils.getUserName(activity) + "$$$$$$$$" //自己的昵称
+ SpUtils.getHeadimg(activity).split("files/")[1] + "$$$$$$$$" //自己的头像
+ link_user_id + "$$$$$$$$" //对方id
+ link_user_name + "$$$$$$$$" //对放昵称
+ link_user_image + "$$$$$$$$" //对方头像
+ "0" + "$$$$$$$$" //消息类型(自己还是对方)
+ "empty" + "$$$$$$$$" //消息字符串 msg
+ imgHttpPath; //图片服务器地址 且不带http前缀
// et_text.clearComposingText();
webSocketClient.send(con);
myTalkBean_img.image = imgHttpPath;
chatListAdapter.notifyDataSetChanged();
recycleview.scrollToPosition(datas.size() - 1);
} else {
ToastUtils.showToast(activity.getResources().getString(R.string.result_upload_error));
}
}
});
// 如果图片还没有回收,强制回收,防止OOM异常
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
System.gc();
}
}
}
/**
* 获取单个人的聊天历史记录
*
* @param link_uid
*/
private void onLoadToGetSomeBodyChatHistory(String link_uid) {
String uid = SpUtils.getUserId(activity);
ServiceApi.getSomeBodyChatHistory(isMailChat, uid, link_uid, new MyString2Callback() {
@Override
public void onError(Call call, Exception e) {
ToastUtils.showInternetErrorToast();
}
@Override
public void onResponse(Call call, String s) {
Log.d(TAG, "onResponse() returned: " + s);
Type type = new TypeToken>() {
}.getType();
List dataBean = new Gson().fromJson(s, type);
if (dataBean != null && dataBean.size() != 0) {
datas = dataBean.get(0).talkDataList;
if (datas != null && datas.size() != 0) {
chatListAdapter = new ChatListAdapter(activity, datas);
recycleview.setAdapter(chatListAdapter);
if (datas.size() != 0) {
recycleview.scrollToPosition(datas.size() - 1);
}
}
} else {
chatListAdapter = new ChatListAdapter(activity, datas);
recycleview.setAdapter(chatListAdapter);
}
}
});
}
}
我们分析下这里的主要方法。
1.首先要添加WebSocket 的依赖,不然谁让你用啊。
compile 'org.java-websocket:Java-WebSocket:1.3.0'
2.看initData();这里初始化WebSocket :initWebSocketClient();
@Override
protected void initData() {
// tv_title.setText("连接中...");
if (isMailChat) {
rl_titlebar.setBackgroundColor(getResources().getColor(R.color.titlebar_bg_blue));
} else {
rl_titlebar.setBackgroundColor(getResources().getColor(R.color.titlebar_bg));
}
chatListAdapter = new ChatListAdapter(activity, datas);//聊天列表
initWebSocketClient();//初始化webSocket
chatBean = (ChatHistoryBean) getIntent().getSerializableExtra("bean");
if (chatBean == null) {
//如果为null,表示不是从聊天历史记录中跳转过来的,而是从其他页面点击"联系我们"跳转进来的
link_user_id = getIntent().getStringExtra("link_id");
link_user_name = getIntent().getStringExtra("link_user_name");
link_user_image = getIntent().getStringExtra("link_user_image");
//获取单个人的聊天历史记录
onLoadToGetSomeBodyChatHistory(link_user_id);
} else {
link_user_id = chatBean.link_user_id;
link_user_name = chatBean.link_user_name;
link_user_image = chatBean.link_user_image;
datas = chatBean.talkDataList;
chatListAdapter = new ChatListAdapter(activity, datas);
recycleview.setAdapter(chatListAdapter);
if (datas.size() != 0) {
recycleview.scrollToPosition(datas.size() - 1);
}
}
}
/**
* 初始化websocket
*/
private void initWebSocketClient() {
URI uri = null;
try {
if (isMailChat) {//员工聊天websocket的连接地址
uri = new URI(NetUtils.WS_CHAT_MAIL + SpUtils.getUserId(activity));
} else {//业务聊天websocket的连接地址
uri = new URI(NetUtils.WS_CHAT_BUSINESS + SpUtils.getUserId(activity));
}
} catch (Exception e) {
e.printStackTrace();
}
//连接中使用的new Draft_17()就是使用的协议version 17(RFC 6455),Tomcat 7.0使用的协议版本为RFC 6455。
Draft_17 draft = new Draft_17();
webSocketClient = new WebSocketClient(uri, draft) {
@Override//连接成功
public void onOpen(ServerHandshake handshakedata) {
Log.d(TAG, "run() returned: " + "连接到服务器");
String title = getIntent().getStringExtra("title");
changeTitle(title);
}
@Override//从服务端消息(聊天时对方发过来的消息)
public void onMessage(final String message) {
Log.d(TAG, "run() returned: " + message);
// 8735$$$$$$$$nature$$$$$$$$a0/51/97/a051979545fa49f18444fc7b2d8f6477.png$$$$$$$$8678$$$$$$$$anan$$$$$$$$db/cd/97/dbcd979f580049ae8cb6f0cbf5969f70.png$$$$$$$$0$$$$$$$$Zzzzz$$$$$$$$empty
// 字符串$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$字符串 用$$$$$$$$分割
// /****自己的id**自己的昵称**自己的头像***对方id***对放昵称**对方头像****消息类型(自己还是对方)****消息字符串****图片****/
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
String messages[] = message.split("\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024");//$$$$$$$$分割
myTalkBean = new ChatHistoryBean.TalkDataListBean();
myTalkBean.user_id = messages[0];
myTalkBean.user_name = messages[1];
myTalkBean.user_image = messages[2];
myTalkBean.link_user_id = messages[3];
myTalkBean.link_user_name = messages[4];
myTalkBean.link_user_image = messages[5];
myTalkBean.type = "1";
myTalkBean.text = messages[7];
myTalkBean.image = messages[8];
myTalkBean.time = sdf.format(date_time);
datas.add(myTalkBean);
chatListAdapter.notifyDataSetChanged();
recycleview.smoothScrollToPosition(datas.size() - 1);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
这里有四个方法,要格外注意:
onOpen():方法是链接成功是调用。
onMessage();方法是从服务端消息(聊天时对方发过来的消息),这里显示的内容对方发过来的内容。
@Override//从服务端消息(聊天时对方发过来的消息)
public void onMessage(final String message) {
Log.d(TAG, "run() returned: " + message);
// 8735$$$$$$$$nature$$$$$$$$a0/51/97/a051979545fa49f18444fc7b2d8f6477.png$$$$$$$$8678$$$$$$$$anan$$$$$$$$db/cd/97/dbcd979f580049ae8cb6f0cbf5969f70.png$$$$$$$$0$$$$$$$$Zzzzz$$$$$$$$empty
// 字符串$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$字符串 用$$$$$$$$分割
// /****自己的id**自己的昵称**自己的头像***对方id***对放昵称**对方头像****消息类型(自己还是对方)****消息字符串****图片****/
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
String messages[] = message.split("\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024\\u0024");//$$$$$$$$分割
myTalkBean = new ChatHistoryBean.TalkDataListBean();
myTalkBean.user_id = messages[0];
myTalkBean.user_name = messages[1];
myTalkBean.user_image = messages[2];
myTalkBean.link_user_id = messages[3];
myTalkBean.link_user_name = messages[4];
myTalkBean.link_user_image = messages[5];
myTalkBean.type = "1";
myTalkBean.text = messages[7];
myTalkBean.image = messages[8];
myTalkBean.time = sdf.format(date_time);
datas.add(myTalkBean);
chatListAdapter.notifyDataSetChanged();
recycleview.smoothScrollToPosition(datas.size() - 1);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
获取到内容后要刷新Adapter,才能显示。
onClose();连接断开,remote判定是客户端断开还是服务端断开
onError();链接错误。
但是不要忘记加上:webSocketClient.connect(); 才能链接到WebSocket.
接下来看是如何发送消息的:
/**
* 发送文本消息
*/
private void sendMessage() {
//字符串$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$%@$$$$$$$$字符串 用$$$$$$$$分割
// /****自己的id**自己的昵称**自己的头像***对方id***对放昵称**对方头像****消息类型(自己还是对方)****消息字符串****图片****/
String img;
content = et_text.getEditableText().toString();
if (StringUtils.isEmpty(content)) {
content = "empty";
return;
} else {
img = "empty";
}
runOnUiThread(new Runnable() {
@Override
public void run() {//webSocketClient发送的文字消息,不是图片
String con = SpUtils.getUserId(activity) + "$$$$$$$$" //自己的id
+ SpUtils.getUserName(activity) + "$$$$$$$$" //自己的昵称
+ SpUtils.getHeadimg(activity).split("files/")[1] + "$$$$$$$$" //自己的头像
+ link_user_id + "$$$$$$$$" //对方id
+ link_user_name + "$$$$$$$$" //对放昵称
+ link_user_image + "$$$$$$$$" //对方头像
+ "0" + "$$$$$$$$" //消息类型(自己还是对方)
+ content + "$$$$$$$$" //消息字符串 msg
+ "empty"; //图片服务器地址 且不带http前缀
et_text.setText("");
webSocketClient.send(con);
}
});
}
webSocketClient.send(con);是发送消息所用,发送的内容。这里发送消息只是发送到后台并没有及时的去更新我们的消息列表,怎么办呢?
//刷新chatListAdapter
myTalkBean = new ChatHistoryBean.TalkDataListBean();
myTalkBean.time = sdf.format(date_time);
myTalkBean.user_id = SpUtils.getUserId(activity);
myTalkBean.user_name = SpUtils.getUserName(activity);
myTalkBean.user_image = SpUtils.getHeadimg(activity).split("files/")[1];
myTalkBean.link_user_id = link_user_id;
myTalkBean.link_user_name = link_user_name;
myTalkBean.link_user_image = link_user_image;
myTalkBean.type = "0";
myTalkBean.text = content;
myTalkBean.image = "empty";
datas.add(myTalkBean);
chatListAdapter.notifyDataSetChanged();
recycleview.scrollToPosition(datas.size() - 1);
我们手动去给bean赋值,是我们发送的内容,然后刷新Adapter。这样我们就能完成文字聊天了。
还有一个方法:
recycleview.smoothScrollToPosition(datas.size() - 1);
想必都很好奇,既然已经刷新过了还调用他干嘛,哈哈哈。。。 我们看下效果图你就明白了
这是添加recycleview.smoothScrollToPosition(datas.size() - 1); 的效果;
大家看到当每收到一条消息后都会显示出来。
这是没有添加这句的效果:
大家看下就能发现问题。列表刷新后我们并没有立即看到,还有向上滑动才显示内容。
我们再看下图片的发送,这里推按只能发送一张每次,首先是上传图片,然后是为Bean赋值,刷新Adapter,这里离我急不介绍了。
不过有一点还要注意,
// 如果图片还没有回收,强制回收,防止OOM异常
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
System.gc();
}
强制回收,防止OOM.
当我们销毁页面时:WebSocket断开连接。
@Override
protected void onDestroy() {
webSocketClient.close();
super.onDestroy();
}