您的位置:首页 > 移动开发 > Android开发

Android中使用webSocket实现文字及单张图片发送聊天功能

2017-06-21 17:41 961 查看
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

1. WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;
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<ChatHistoryBean.TalkDataListBean> 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();

4000
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<ArrayList<ChatHistoryBean>>() {
}.getType();
List<ChatHistoryBean> 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);
}
}
}


方法initWebSocketClient();初始化WebSocket

/**
* 初始化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();
}
}
});
}


这里参考下文献:http://blog.csdn.net/tangxl2008008/article/details/52421413

这里有四个方法,要格外注意:
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.printStackT
b0c5
race();
}
}
});
}
获取到内容后要刷新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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐