一.总结
1.如何支持连续的消息发送,并且不会产生线程安全的问题
//存放正在发送的消息,key 为生成的一个临时messageID(msgTag),value为Message实体
//当消息发送成功后,从mSendingMsgs删除对应的Message实体
private SparseArray<MessageDetail> mSendingMsgs = new SparseArray<MessageDetail>();
2.一些分析
本设计应该是较为常见的聊天设计方案。其实近一年来,我也主要参与了公司聊天页面的开发和设计,本方案和我们公司的设计细节还是不太相符,不过整体的架构有很大的参考价值。
其实原理非常简单:就是一个listview,然后动态的修改listview中某个item的状态。
二.具体流程
1.点击发送按钮:
if (!AppContext.getInstance().isLogin()) {
UIHelper.showLoginActivity(getActivity());
return;
}
if (StringUtils.isEmpty(str)) {
AppContext.showToastShort(R.string.tip_content_empty);
return;
}
MessageDetail message = new MessageDetail();
User user = AppContext.getInstance().getLoginUser();
int msgTag = mMsgTag++;
message.setId(msgTag);
message.setPortrait(user.getPortrait());
message.setAuthor(user.getName());
message.setAuthorId(user.getId());
message.setContent(str.toString());
sendMessage(message);
说明:
即每个消息都是有一个本地的id的,作为区分
2.发送消息调用的具体方法分析:
/** * 发送消息 * @param msg */
private void sendMessage(MessageDetail msg){
msg.setStatus(MessageDetail.MessageStatus.SENDING);
Date date = new Date();
msg.setPubDate(net.oschina.app.util.StringUtils.getDateString(date));
//如果此次发表的时间距离上次的时间达到了 TIME_INTERVAL 的间隔要求,则显示时间
if(isNeedShowDate(date.getTime(),mLastShowDate)) {
msg.setShowDate(true);
mLastShowDate = date.getTime();
}
//如果待发送列表没有此条消息,说明是新消息,不是发送失败再次发送的,不需要再次添加
if(mSendingMsgs.indexOfKey(msg.getId())<0) {
mSendingMsgs.put(msg.getId(), msg);
mAdapter.addItem(0, msg);
mListView.setSelection(mListView.getBottom());
}else{
mAdapter.notifyDataSetChanged();
}
OSChinaApi.publicMessage(msg.getAuthorId(), mFid, msg.getContent(), new SendMessageResponseHandler(msg.getId()));
}
说明:
- 如果待发送消息列表中没有这条消息,则把消息加入到待发送消息列表中,并刷新聊天列表。
- 调用发送消息的网络请求接口。
3.发送接口具体实现操作:
class SendMessageResponseHandler extends AsyncHttpResponseHandler{
private int msgTag;
public SendMessageResponseHandler(int msgTag) {
this.msgTag = msgTag;
}
@Override
public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {
try {
ResultBean resb = XmlUtils.toBean(ResultBean.class,
new ByteArrayInputStream(arg2));
Result res = resb.getResult();
if (res.OK()) {
//从mSendingMsgs获取发送时放入的MessageDetail实体
MessageDetail message = mSendingMsgs.get(this.msgTag);
MessageDetail serverMsg = resb.getMessage();
//把id设置为服务器返回的id
message.setId(serverMsg.getId());
message.setStatus(MessageDetail.MessageStatus.NORMAL);
//从待发送列表移除
mSendingMsgs.remove(this.msgTag);
mAdapter.notifyDataSetChanged();
} else {
error();
AppContext.showToastShort(res.getErrorMessage());
}
emojiFragment.clean();
} catch (Exception e) {
e.printStackTrace();
onFailure(arg0, arg1, arg2, e);
}
}
@Override
public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) {
error();
}
private void error(){
mSendingMsgs.get(this.msgTag).setStatus(MessageDetail.MessageStatus.ERROR);
mAdapter.notifyDataSetChanged();
}
}
说明:
- 需要把消息的tag传递进来。
- 发送成功的话,从mSendingMsgs获取发送时放入的MessageDetail实体,把id设置为服务器返回的id,并改变本地消息的状态,并从待发送列表中删除本地消息,刷新列表。是不会从服务端再拉取一遍的。
- 发送失败的话,设置发送的消息的状态为失败,并刷新列表。
4.失败点击重发的实现方案:
采用的是接口回调的方案。
Adapter:
public interface OnRetrySendMessageListener {
void onRetrySendMessage(int msgId);
}
/** * 重试发送 * * @param v */
@OnClick(R.id.itv_error)
void retry(View v) {
if (v.getTag() != null && mOnRetrySendMessageListener != null) {
mOnRetrySendMessageListener.onRetrySendMessage((int) v.getTag());
}
}
}
Fragment:该fragment实现了Adapter的重发接口
public class MessageDetailFragment extends BaseListFragment<MessageDetail> implements OnItemLongClickListener, OnSendClickListener,MessageDetailAdapter.OnRetrySendMessageListener
adapter.setOnRetrySendMessageListener(this);
@Override
public void onRetrySendMessage(int msgId) {
MessageDetail message = mSendingMsgs.get(msgId);
if (message != null) {
sendMessage(message);
}
}
5.每条消息的状态:
在实体中有一个发送状态的字段,通过不断修改该值来实现消息发送状态的切换。