【oschina android源码分析】聊天页面(私信)的设计

一.总结

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.每条消息的状态:

在实体中有一个发送状态的字段,通过不断修改该值来实现消息发送状态的切换。

你可能感兴趣的:(【oschina android源码分析】聊天页面(私信)的设计)