1. 程式人生 > >老版本的環信,實現訊息撤回功能。

老版本的環信,實現訊息撤回功能。

最近公司很久之前的一個整合過環信聊天功能的app,要增加訊息撤回的功能,由於這個app是很久之前的。。所以,那個時候的easeUi裡面並沒有這個功能,於是就參照有這個功能的sdk,把程式碼搬了過來。。
Ps:當前的sdk版本是2.2.4

首先找到聊天頁面ChatActivity頁面,裡面是一個EaseUi裡面的EaseChatFragment,然後找到訊息框點選事件,messageList有個item點選事件,(EaseChatMessageList.MessageListItemClickListener),有一系列事件,你可以寫在訊息框點選事件裡(onBubbleClick),也可以寫在訊息框長按事件裡面(onBubbleLongClick),我是選擇了後者。

原理圖:
這裡寫圖片描述

下面是效果圖:
這裡寫圖片描述

下面貼程式碼:

messageList.setItemClickListener(new EaseChatMessageList.MessageListItemClickListener() {

            @Override
            public void onUserAvatarClick(String username) {
                if (chatFragmentListener != null) {
                    chatFragmentListener.onAvatarClick(username);
                }
            }

            @Override
public void onResendClick(final EMMessage message) { new EaseAlertDialog(getActivity(), R.string.resend, R.string.confirm_resend, null, new AlertDialogUser() { @Override public void onResult(boolean confirmed, Bundle bundle) { if
(!confirmed) { return; } resendMessage(message); } }, true).show(); } @Override public void onBubbleLongClick(EMMessage message) { contextMenuMessage = message; //這個是用來控制撤回這個選單是否顯示 boolean isvisibilty = false; /** * callback:這個用來判斷是否已經是撤回的訊息, * 在訊息撤回以後,會發送一條新的訊息,內容是XX * 撤回了一條訊息(我暫且叫CallMessage) * 我在這個CallMessage中加了個擴充套件欄位callback,來標識該條訊息已經是 * 撤回的訊息了。即再長按該條訊息不需要再顯示撤回這個選單了 * */ boolean callback = false; try { callback = message.getBooleanAttribute("callback");//如果已經是撤回的訊息了,就不要顯示撤回這個選項了 } catch (EaseMobException e) { e.printStackTrace(); } /** * 這裡需要判斷下該條訊息必須是自己發的,並且不是CallMessage * 才能顯示撤回選單 */ if (message.getFrom().equals(user.getHuanxinId()) && !callback) { isvisibilty = true; } else { isvisibilty = false; } //長按以後彈出一個選擇框(包括複製訊息,刪除訊息,和撤回訊息) startActivityForResult((new Intent(getActivity(), ContextMenuActivity.class)) .putExtra("message", message) .putExtra("ischatroom", chatType == EaseConstant.CHATTYPE_CHATROOM) .putExtra("isvisibilty", isvisibilty), REQUEST_CODE_CONTEXT_MENU); } @Override public boolean onBubbleClick(EMMessage message) { if (chatFragmentListener != null) { return chatFragmentListener.onMessageBubbleClick(message); } return false; } });

選擇框的樣式:
這裡寫圖片描述

下面是ContextMenuActivity.class(選擇框)

import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import com.easemob.chat.EMMessage;
import com.easemob.easeui.R;
import com.easemob.easeui.utils.Constant;

//import com.easemob.redpacketsdk.constant.RPConstant;
//import com.hyphenate.chat.EMMessage;
//import com.hyphenate.chatuidemo.Constant;
//import com.hyphenate.chatuidemo.R;

/**
 * 選擇框頁面,根據不同的type,來配置不同的佈局,因為有的不需要有複製訊息這個選單
 */
public class ContextMenuActivity extends EaseBaseActivity {
    public static final int RESULT_CODE_COPY = 1;
    public static final int RESULT_CODE_DELETE = 2;
    public static final int RESULT_CODE_FORWARD = 3;
    public static final int RESULT_CODE_RECALL = 4;
    private EMMessage message;
    Intent intent;
    boolean isVisibilty;//撤回是否可見
    private TextView tvReCall;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        intent=getIntent();
         message = intent.getParcelableExtra("message");
        isVisibilty=intent.getBooleanExtra("isvisibilty",false);
//      boolean isChatroom = getIntent().getBooleanExtra("ischatroom", false);

        int type = message.getType().ordinal();
        if (type == EMMessage.Type.TXT.ordinal()) {
//          if(message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_VIDEO_CALL, false)
//                  || message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_VOICE_CALL, false)
//                  //red packet code : 遮蔽紅包訊息的轉發功能
//                  || message.getBooleanAttribute(RPConstant.MESSAGE_ATTR_IS_RED_PACKET_MESSAGE, false)){
//                  //end of red packet code
//              setContentView(R.layout.em_context_menu_for_location);
//          }else
            if(message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_BIG_EXPRESSION, false)){
                setContentView(R.layout.em_context_menu_for_image);
            }else{
                setContentView(R.layout.em_context_menu_for_text);
            }
        } else if (type == EMMessage.Type.LOCATION.ordinal()) {
            setContentView(R.layout.em_context_menu_for_location);
        } else if (type == EMMessage.Type.IMAGE.ordinal()) {
            setContentView(R.layout.em_context_menu_for_image);
        } else if (type == EMMessage.Type.VOICE.ordinal()) {
            setContentView(R.layout.em_context_menu_for_voice);
        } else if (type == EMMessage.Type.VIDEO.ordinal()) {
            setContentView(R.layout.em_context_menu_for_video);
        } else if (type == EMMessage.Type.FILE.ordinal()) {
            setContentView(R.layout.em_context_menu_for_location);
        }
        tvReCall= (TextView) findViewById(R.id.recall);
        if(isVisibilty){
            tvReCall.setVisibility(View.VISIBLE);
        }else{
            tvReCall.setVisibility(View.GONE);
        }
//      if (isChatroom
//              //red packet code : 遮蔽紅包訊息的撤回功能
//              || message.getBooleanAttribute(RPConstant.MESSAGE_ATTR_IS_RED_PACKET_MESSAGE, false)) {
//              //end of red packet code
//          View v = (View) findViewById(R.id.forward);
//          if (v != null) {
//              v.setVisibility(View.GONE);
//          }
//      }
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        finish();
        return true;
    }

    public void copy(View view){
        setResult(RESULT_CODE_COPY);
        finish();
    }
    public void delete(View view){
        setResult(RESULT_CODE_DELETE);
        finish();
    }
    public void forward(View view){
        setResult(RESULT_CODE_FORWARD);
        finish();
    }
    public  void recall(View view){
        setResult(RESULT_CODE_RECALL);
        finish();
    }

}

下面是佈局:em_context_menu_for_image:

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <!-- <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="1dp"
        android:background="@drawable/em_context_menu_item_bg"
        android:clickable="true"
        android:gravity="center_vertical"
        android:onClick="copy"
        android:padding="10dp"
        android:text="@string/copy"
        android:textColor="@android:color/black"
        android:textSize="20sp" /> -->

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:color/darker_gray" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/em_context_menu_item_bg"
        android:clickable="true"
        android:gravity="center_vertical"
        android:onClick="delete"
        android:padding="10dp"
        android:text="@string/delete"
        android:textColor="@android:color/black"
        android:textSize="20sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:color/darker_gray" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/forward"
        android:background="@drawable/em_context_menu_item_bg"
        android:clickable="true"
        android:gravity="center_vertical"
        android:onClick="forward"
        android:padding="10dp"
        android:text="@string/forward"
        android:visibility="gone"
        android:textColor="@android:color/black"
        android:textSize="20sp" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/recall"
        android:background="@drawable/em_context_menu_item_bg"
        android:clickable="true"
        android:gravity="center_vertical"
        android:onClick="recall"
        android:padding="10dp"
        android:text="@string/recall"
        android:textColor="@android:color/black"
        android:textSize="20sp" />

</LinearLayout>

其他的佈局大同小異,就不一一貼出來了。
然後選擇好是哪個選單以後,選擇框消失,回到聊天介面,
即EaseChatFragment的onActivityResult裡面。。

if (requestCode == REQUEST_CODE_CONTEXT_MENU) {
            switch (resultCode) {
                case ContextMenuActivity.RESULT_CODE_COPY: // copy
//                    MessageBody body = contextMenuMessage.getBody();
                    EMMessage.Type type = contextMenuMessage.getType();
                    if (type.equals(EMMessage.Type.TXT)) {
                        TextMessageBody txtBody = (TextMessageBody) contextMenuMessage.getBody();
                        clipboard.setText(txtBody.getMessage());
                    }
                    break;
                case ContextMenuActivity.RESULT_CODE_DELETE: // delete
                    conversation.removeMessage(contextMenuMessage.getMsgId());
                    messageList.refresh();
                    break;
                case ContextMenuActivity.RESULT_CODE_FORWARD: // forward
                    break;
                case ContextMenuActivity.RESULT_CODE_RECALL://撤回
                    recall();
                    break;
                default:
                    break;
            }
        }

重點看下recall()方法,這個就是撤回功能。

 /**
     * 撤回
     * 原理:
     * A使用者傳送訊息。
     * A使用者需要撤回某條訊息,將訊息id通過擴充套件訊息傳送到使用者B。
     * B使用者收到擴充套件訊息,解析其中的messageid,從資料庫刪除對應訊息。
     */
    private void recall() {

//        sendTextMessage();
        EMMessage cmdMsg = EMMessage.createSendMessage(EMMessage.Type.CMD);
// 如果是群聊,設定chattype,預設是單聊
        if (chatType == EaseConstant.CHATTYPE_GROUP) {
            cmdMsg.setChatType(ChatType.GroupChat);
        } else if (chatType == EaseConstant.CHATTYPE_CHATROOM) {
            cmdMsg.setChatType(ChatType.ChatRoom);
        }
        String action = "REVOKE_FLAG";
        CmdMessageBody cmdBody = new CmdMessageBody(action);
// 設定訊息body
        cmdMsg.addBody(cmdBody);
// 設定要發給誰,使用者username或者群聊groupid
        cmdMsg.setReceipt(toChatUsername);
// 通過擴充套件欄位新增要撤回訊息的id
        cmdMsg.setAttribute("msgId", contextMenuMessage.getMsgId());
        cmdMsg.setAttribute("name", user.getName());
        EMChatManager.getInstance().sendMessage(cmdMsg, new EMCallBack() {
            @Override
            public void onSuccess() {
//                Log.e("zmm","傳送成功-->");
                /**
                 * 注意這裡一定要把需要撤回的訊息刪除,這裡如果不刪除,會出現,
                 * 自己這邊的聊天列表裡面該條訊息還在。
                 */
                //傳送成功以後再這裡把該撤回的訊息刪除,這裡是為了重新整理自己的聊天頁面
                conversation.removeMessage(contextMenuMessage.getMsgId());
//                Log.e("zmm", "刪除訊息-->" + contextMenuMessage.getMsgId());
/**2018-05-11更新:可以在傳送透傳訊息成功以後,就傳送一個“xx撤回一個訊息”的普通訊息,更好的處理。應該是。接收方接收到透傳。並且刪除掉你要撤回的訊息以後。那個時候。傳送一個透傳訊息。告訴你。你再來發送“xx撤回一個訊息”這樣的一條訊息比較合理。**/
                EMMessage message = EMMessage.createTxtSendMessage(user.getName() + "回撤一條訊息",
                        toChatUsername);
                message.setAttribute("callback", true);
                sendMessage(message, user.getName());
            }

            @Override
            public void onProgress(int progress, String status) {

            }

            @Override
            public void onError(int code, String error) {
                Log.e("zmm", "傳送失敗-->" + code + "-->" + error);
            }
        });
        messageList.refreshSelectLast();
    }
 protected void sendMessage(EMMessage message, String name) {
        if (chatFragmentListener != null) {
            //設定擴充套件屬性
            chatFragmentListener.onSetMessageAttributes(message);
        }
        // 如果是群聊,設定chattype,預設是單聊
        if (chatType == EaseConstant.CHATTYPE_GROUP) {
            message.setChatType(ChatType.GroupChat);
        } else if (chatType == EaseConstant.CHATTYPE_CHATROOM) {
            message.setChatType(ChatType.ChatRoom);
        }
        message.setAttribute("name", name);//這裡放入傳送人的名字,在需要拿到傳送者名字的時候再取出來。(XX)
        //傳送訊息
        EMChatManager.getInstance().sendMessage(message, null);
        //重新整理ui
        messageList.refreshSelectLast();
    }

然後就是在接收到該條CallMessage的地方處理下就可以了。
還是再EaseChatFragment這個頁面,實現了EMEventListener這個介面,裡有接收到新訊息的回撥:

{
        EMMessage message = (EMMessage) event.getData();
//        Log.e("zmm","onEvent-->"+event.getEvent());
        switch (event.getEvent()) {
            case EventNewMessage:
                // 獲取到message
                String username = null;
                // 群組訊息
                if (message.getChatType() == ChatType.GroupChat || message.getChatType() == ChatType.ChatRoom) {
                    username = message.getTo();
                } else {
                    // 單聊訊息
                    username = message.getFrom();
                }

                // 如果是當前會話的訊息,重新整理聊天頁面
                if (username.equals(toChatUsername)) {
                    messageList.refreshSelectLast();
                    // 聲音和震動提示有新訊息
                    EaseUI.getInstance().getNotifier().viberateAndPlayTone(message);
                } else {
                    // 如果訊息不是和當前聊天ID的訊息
                    EaseUI.getInstance().getNotifier().onNewMsg(message);
                }

                break;
            case EventDeliveryAck:
            case EventReadAck:
                // 獲取到message
                messageList.refresh();
                break;
            case EventOfflineMessage:
                // a list of offline messages
                // List<EMMessage> offlineMessages = (List<EMMessage>)
                // event.getData();
                messageList.refresh();
                break;
            case EventNewCMDMessage: // CMD訊息(這個就是CallMessage返回的type)
//                Log.e("zmm", "接收到回撥訊息-->");
                CmdMessageBody cmdMsgBody = (CmdMessageBody) message.getBody();
                String action = cmdMsgBody.action;//獲取自定義action
                String name = null;
                try {
                    name = message.getStringAttribute("name");
                } catch (EaseMobException e) {
                    Log.e("zmm", "-->" + e.toString() + "-->" + e.getMessage());
                    e.printStackTrace();
                }
                if (action.equals("REVOKE_FLAG")) {
                    try {
                        String msgId = message.getStringAttribute("msgId");
                        EMConversation conversation = EMChatManager.getInstance().getConversation(message.getFrom());
//                        --刪除訊息來表示撤回--
//                        Log.e("zmm",)
                        conversation.removeMessage(msgId);
//                        Log.e("zmm", "刪除訊息-->" + msgId);
//2018 -05-11更新:不應該再這裡傳送“XX撤回了一條訊息。”。
                        //刪除訊息以後增加一條XX撤回了一條訊息。
                        //傳送一個透傳訊息給撤回方
                        addcallbackmessage();
                    } catch (EaseMobException e) {
                        // TODO Auto-generated catch block
                        Log.e("zmm", "1111-->" + e.toString() + "-->" + e.getMessage());
                        e.printStackTrace();
                    }
                }else if ("RECALLBACK_FLAG".equals(action)) {
                //這裡是撤回方接收到。對方已經刪除了我需要撤回的訊息了。這個時候。我要傳送一個“xx撤回了一條訊息”這樣一個普通訊息。
                    EMMessage newmessage = EMMessage.createTxtSendMessage(user.getName() + "回撤一條訊息",
                            toChatUsername);
                    newmessage.setAttribute("callback", true);
                    sendMessage(newmessage, name);

                }
                break;
            default:
                break;
        }

    }


/**
     * 發透傳訊息告訴需要撤回訊息的那位。告訴他我已經成功刪除訊息了
     * 你可以再發送一條普通訊息,內容是“XX撤回了一條訊息”
     */
    private void addcallbackmessage() {
        EMMessage cmdMsg = EMMessage.createSendMessage(EMMessage.Type.CMD);
// 如果是群聊,設定chattype,預設是單聊
        if (chatType == EaseConstant.CHATTYPE_GROUP) {
            cmdMsg.setChatType(ChatType.GroupChat);
        } else if (chatType == EaseConstant.CHATTYPE_CHATROOM) {
            cmdMsg.setChatType(ChatType.ChatRoom);
        }
        CmdMessageBody cmdBody = new CmdMessageBody("RECALLBACK_FLAG");
// 設定訊息body
        cmdMsg.addBody(cmdBody);
// 設定要發給誰,使用者username或者群聊groupid
        cmdMsg.setReceipt(toChatUsername);
// 通過擴充套件欄位新增要撤回訊息的id
//        cmdMsg.setAttribute("msgId", contextMenuMessage.getMsgId());
        cmdMsg.setAttribute("name", user.getName());
        EMChatManager.getInstance().sendMessage(cmdMsg, null);


    }

以上就完成了訊息撤回功能。。特此記錄。望對大家有幫助。

最後獻上一句最近喜歡的話:
最怕一生碌碌無為,還說難得平凡可貴。。。。
加油!!!