商城中基本上都添加了客服的功能,是買家和買家更友善的溝通。下面介紹的第三方即時通訊是環信。在客服這一子產品中我們要做的就是,實作客服與買家之間的交流(包括發送文字,表情,圖檔,語音,文檔,地理位置等),發送訂單消息,商品資訊等,實作二者之間的互動,買家給客服打分。補充就是六天紀錄的删除,發送資訊的時間,接接收消息的提醒等。
1、新增賬號,并申請客服賬号在文檔裡做出了詳細的說明,位址:http://docs.easemob.com/cs/300visitoraccess/10nativeapp
2、建立應用,關聯app,
3、下載下傳sdk和環信的的ui架構(ui架構使用與否看自己)
4、這裡為了快速展示,就使用官方提供的ui架構。把下載下傳的sdk考入程式
5、環信的賬号注冊分為兩種,開放注冊和授權注冊。我們要選擇開放注冊才可以讓使用者自由的新增賬號。
6、下面就是整個程式最重要的部分了,聊天界面。
7、商品消息的發送-----繪制用戶端和服務端的軌迹消息。
8、訂單資訊的發送,同樣繪制兩端的軌迹消息。
9、客服邀請使用者打分
10、全局監聽,想要實作消息軌迹的繪制
11、語音
進入代碼開發:
1、首先建立一個單例類,用來初始化EaseUI,對EasaUI進行配置
empty
public class CustomHelper {
private static CustomHelper customhelper;
private String TAG = "CustomHelper";
private Context appContext;
private EaseUI easeUI;
private EMConnectionListener connectionListener;
/**
* EMEventListener
*/
protected EMEventListener eventListener = null;
private boolean alreadyNotified = false;
private CustomHelper() {
}
public synchronized static CustomHelper getInstance() {
if (customhelper == null) {
customhelper = new CustomHelper();
}
return customhelper;
}
public void init(Context context) {
CustomConfigManager.init(context);
EMChat.getInstance().setAppkey(CustomConfig.DEFALUT_APPKEY);
// EMChat.getInstance().setAutoLogin(false);
EaseUI.getInstance().init(context);
if (EaseUI.getInstance().init(context)) {
appContext = context;
// 在小米手機上當app被kill時使用小米推送進行消息提示,SDK已支援,可選
EMChatManager.getInstance().setMipushConfig("2882303761517370134",
"5131737040134");
// 設為調試模式,打成正式包時,最好設為false,以免消耗額外的資源
// EMChat.getInstance().setDebugMode(true);
// get easeui instance
easeUI = EaseUI.getInstance();
// 調用easeui的api設定providers
setEaseUIProviders();
// demoModel = new DemoModel(context);
// 初始化PreferenceManager
// PreferenceManager.init(context);
// 設定全局監聽
setGlobalListeners();
}
}
/**
* 檢測是否為訂單消息或者為軌迹消息
*
* @param message
* @return
*/
public boolean isPictureTxtMessage(EMMessage message) {
JSONObject jsonObj = null;
try {
jsonObj = message
.getJSONObjectAttribute(CustomConfig.MESSAGE_ATTR_MSGTYPE);
} catch (EaseMobException e) {
}
if (jsonObj == null) {
return false;
}
if (jsonObj.has("order") || jsonObj.has("track")) {
return true;
}
return false;
}
/**
* 檢測是否為訂單消息或者為軌迹消息
*
* @param message
* @return
*/
public boolean isTRACKTxtMessage(EMMessage message) {
JSONObject jsonObj = null;
try {
jsonObj = message
.getJSONObjectAttribute(CustomConfig.MESSAGE_ATTR_MSGTYPE);
} catch (EaseMobException e) {
}
if (jsonObj == null) {
return false;
}
if (jsonObj.has("track")) {
return true;
}
return false;
}
/**
* 檢測是否為訂單消息或者為軌迹消息
*
* @param message
* @return
*/
public boolean isORDERTxtMessage(EMMessage message) {
JSONObject jsonObj = null;
try {
jsonObj = message
.getJSONObjectAttribute(CustomConfig.MESSAGE_ATTR_MSGTYPE);
} catch (EaseMobException e) {
}
if (jsonObj == null) {
return false;
}
if (jsonObj.has("order")) {
return true;
}
return false;
}
// //設定頭次昂
protected void setEaseUIProviders() {
// 不設定,則使用easeui預設的
easeUI.getNotifier()
.setNotificationInfoProvider(
new com.easemob.easeui.model.EaseNotifier.EaseNotificationInfoProvider() {
public String getTitle(EMMessage message) {
// 修改标題,這裡使用預設
return null;
}
@Override
public int getSmallIcon(EMMessage message) {
// 設定小圖示,這裡為預設
return 0;
}
// @Override
// public String getDisplayedText(EMMessage message)
// {
// // 設定狀态欄的消息提示,可以根據message的類型做相應提示
// String ticker =
// EaseCommonUtils.getMessageDigest(message,
// appContext);
// if (message.getType() == Type.TXT) {
// ticker = ticker.replaceAll("\\[.{2,3}\\]",
// "[表情]");
// }
// return message.getFrom() + ": " + ticker;
// }
// public String getLatestText(EMMessage message,
// int fromUsersNum, int
// messageNum) {
// return null;
// // return fromUsersNum + "個基友,發來了" + messageNum +
// "條消息";
// }
@Override
public String getDisplayedText(EMMessage message) {
// 設定狀态欄的消息提示,可以根據message的類型做相應提示
String ticker = EaseCommonUtils
.getMessageDigest(message, appContext);
if (message.getType() == EMMessage.Type.TXT) {
ticker = ticker.replaceAll("\\[.{2,3}\\]",
"[表情]");
}
if (message.getFrom().equals(
MLSPUtil.get(appContext,
CustomConfig.DEFALUT_IM, ""))) {
return "具商客服: " + ticker;
} else {
return message.getFrom() + ": " + ticker;
}
}
/**
* 根據消息條數來判斷如果顯示
*
* @param message
* 接收到的消息
* @param fromUsersNum
* 發送人的數量
* @param messageNum
* 消息數量
* @return
*/
@Override
public String getLatestText(EMMessage message,
int fromUsersNum, int messageNum) {
// 當隻有一個人,發來一條消息時,顯示消息内容 TODO 表情符顯示為圖檔
if (fromUsersNum == 1 && messageNum == 1) {
return "具商客服:"
+ EaseCommonUtils.getMessageDigest(
message, appContext)
.replace("\\[.{2,3}\\]",
"[表情]");
} else {
return "具商發來 " + messageNum + " 條消息";
}
}
@Override
public Intent getLaunchIntent(EMMessage message) {
//聯系客服
// if (TextUtils.isEmpty(CustomConfigManager.getInstance().getCurrentIM())) {
String tochatUserName = CustomConfig.DEFALUT_IM;
// } else {
// tochatUserName = CustomConfigManager.getInstance().getCurrentIM();
//
// }
// 設定點選通知欄跳轉事件
Intent intent = new Intent(appContext,
ChatActivity.class);
ChatType chatType = message.getChatType();
if (chatType == ChatType.Chat) { // 單聊資訊
intent.putExtra(EaseConstant.EXTRA_USER_ID,
message.getFrom());
intent.putExtra(
EaseConstant.EXTRA_CHAT_TYPE,
Constant.CHATTYPE_SINGLE);
// intent.putExtra(EaseConstant.EXTRA_SHOW_USERNICK,
// true);
}
return intent;
}
});
// 設定表情provider
easeUI.setEmojiconInfoProvider(new EaseUI.EaseEmojiconInfoProvider() {
@Override
public EaseEmojicon getEmojiconInfo(String emojiconIdentityCode) {
EaseEmojiconGroupEntity data = EmojiconExampleGroupData
.getData();
for (EaseEmojicon emojicon : data.getEmojiconList()) {
if (emojicon.getIdentityCode().equals(emojiconIdentityCode)) {
return emojicon;
}
}
return null;
}
@Override
public Map
getTextEmojiconMapping() {
// 傳回文字表情emoji文本和圖檔(resource id或者本地路徑)的映射map
return null;
}
});
}
/**
* 判斷是否為滿意度調查類型的消息
*
* @param message
* @return
*/
public boolean isCtrlTypeMessage(EMMessage message) {
JSONObject jsonObj = null;
try {
jsonObj = message
.getJSONObjectAttribute(CustomConfig.C_ATTR_KEY_WEICHAT);
if (jsonObj.has(CustomConfig.C_ATTR_CTRLTYPE)) {
String type = jsonObj.getString(CustomConfig.C_ATTR_CTRLTYPE);
if (type.equalsIgnoreCase(CustomConfig.C_ATTR_INVITEENQUIRY)
|| type.equalsIgnoreCase(CustomConfig.C_ATTR_ENQUIRY)) {
return true;
}
}
} catch (EaseMobException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 判斷是否為使用者軌迹類型的消息
*
* @param message
* @return
*/
public boolean isTrackMessage(EMMessage message) {
JSONObject jsonObj = null;
try {
jsonObj = message
.getJSONObjectAttribute(CustomConfig.C_ATTR_KEY_MSGTYPE);
if (jsonObj.has(CustomConfig.C_ATTR_TRACK)) {
return true;
}
} catch (EaseMobException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 判斷是否為訂單消息
*
* @param message
* @return
*/
public boolean isOrderFormMessage(EMMessage message) {
JSONObject jsonObj = null;
try {
jsonObj = message
.getJSONObjectAttribute(CustomConfig.C_ATTR_KEY_MSGTYPE);
if (jsonObj.has(CustomConfig.C_ATTR_ORDER)) {
return true;
}
} catch (EaseMobException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 設定全局事件監聽
*/
protected void setGlobalListeners() {
// create the global connection listener
connectionListener = new EMConnectionListener() {
@Override
public void onDisconnected(int error) {
if (error == EMError.USER_REMOVED) {
onCurrentAccountRemoved();
} else if (error == EMError.CONNECTION_CONFLICT) {
onConnectionConflict();
}
}
@Override
public void onConnected() {
// in case group and contact were already synced, we supposed to
// notify sdk we are ready to receive the events
CustomHelper.getInstance().notifyForRecevingEvents();
}
};
// 注冊連接配接監聽
EMChatManager.getInstance().addConnectionListener(connectionListener);
// 注冊消息事件監聽
registerEventListener();
}
/**
* 賬号在别的裝置登入
*/
protected void onConnectionConflict() {
Intent intent = new Intent(appContext, MainFragment.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Constant.ACCOUNT_CONFLICT, true);
appContext.startActivity(intent);
// popActivity(appContext);
((Activity) appContext).finish();
}
/**
* 賬号被移除
*/
protected void onCurrentAccountRemoved() {
Intent intent = new Intent(appContext, MainFragment.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Constant.ACCOUNT_REMOVED, true);
appContext.startActivity(intent);
((Activity) appContext).finish();
}
/**
* 全局事件監聽 因為可能會有UI頁面先處理到這個消息,是以一般如果UI頁面已經處理,這裡就不需要再次處理 activityList.size()
* <= 0 意味着所有頁面都已經在背景運作,或者已經離開Activity Stack
*/
protected void registerEventListener() {
eventListener = new EMEventListener() {
private BroadcastReceiver broadCastReceiver = null;
@Override
public void onEvent(EMNotifierEvent event) {
EMMessage message = null;
if (event.getData() instanceof EMMessage) {
message = (EMMessage) event.getData();
EMLog.d(TAG, "receive the event : " + event.getEvent()
+ ",id : " + message.getMsgId());
}
switch (event.getEvent()) {
case EventNewMessage:
// 應用在背景,不需要重新整理UI,通知欄提示新消息
if (!easeUI.hasForegroundActivies()) {
getNotifier().onNewMsg(message);
}
break;
case EventOfflineMessage:
if (!easeUI.hasForegroundActivies()) {
EMLog.d(TAG, "received offline messages");
List
messages = (List
) event
.getData();
getNotifier().onNewMesg(messages);
}
break;
// below is just giving a example to show a cmd toast, the app
// should not follow this
// so be careful of this
case EventNewCMDMessage: {
EMLog.d(TAG, "收到透傳消息");
// 擷取消息body
CmdMessageBody cmdMsgBody = (CmdMessageBody) message
.getBody();
final String action = cmdMsgBody.action;// 擷取自定義action
// 擷取擴充屬性 此處省略
// message.getStringAttribute("");
EMLog.d(TAG, String.format("透傳消息:action:%s,message:%s",
action, message.toString()));
final String str = appContext
.getString(R.string.receive_the_passthrough);
final String CMD_TOAST_BROADCAST = "easemob.demo.cmd.toast";
IntentFilter cmdFilter = new IntentFilter(
CMD_TOAST_BROADCAST);
if (broadCastReceiver == null) {
broadCastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(appContext,
intent.getStringExtra("cmd_value"),
Toast.LENGTH_SHORT).show();
}
};
// 注冊廣播接收者
appContext.registerReceiver(broadCastReceiver,
cmdFilter);
}
Intent broadcastIntent = new Intent(CMD_TOAST_BROADCAST);
broadcastIntent.putExtra("cmd_value", str + action);
appContext.sendBroadcast(broadcastIntent, null);
break;
}
case EventDeliveryAck:
message.setDelivered(true);
break;
case EventReadAck:
message.setAcked(true);
break;
// add other events in case you are interested in
default:
break;
}
}
};
EMChatManager.getInstance().registerEventListener(eventListener);
}
/**
* 是否登入成功過
*
* @return
*/
public boolean isLoggedIn() {
return EMChat.getInstance().isLoggedIn();
}
/**
* 登出
*
* @param unbindDeviceToken
* 是否解綁裝置token(使用GCM才有)
* @param callback
* callback
*/
public void logout(boolean unbindDeviceToken, final EMCallBack callback) {
EMChatManager.getInstance().logout(unbindDeviceToken, new EMCallBack() {
@Override
public void onSuccess() {
if (callback != null) {
callback.onSuccess();
}
}
@Override
public void onProgress(int progress, String status) {
if (callback != null) {
callback.onProgress(progress, status);
}
}
@Override
public void onError(int code, String error) {
if (callback != null) {
callback.onError(code, error);
}
}
});
}
/**
* 擷取消息通知類
*
* @return
*/
public EaseNotifier getNotifier() {
return easeUI.getNotifier();
}
public synchronized void notifyForRecevingEvents() {
if (alreadyNotified) {
return;
}
// 通知sdk,UI 已經初始化完畢,注冊了相應的receiver和listener, 可以接受broadcast了
EMChat.getInstance().setAppInited();
alreadyNotified = true;
}
}
2、接着就要在application做一些全局工作
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcVTQE50dFdVZwZ1RhZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TM3MDM0ETM3EDNxITM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
3、最重要的就是要實作聊天和商品資訊的發送和軌迹繪制
a、通過官方的文檔,我們可以看出,發送文本資訊要用
b、商品的軌迹繪制,資訊需要包括商品名稱,價格,圖檔,連結,說明。消息展示則需要我們自己定義排版,在布局中的控件的id名稱則需要和EasaUI中定義的一樣,否則會出錯,親身踩坑。
empty
/**
* 監聽事件 父類
*/
@Override
protected void setUpView() {
setChatFragmentHelper(this);
super.setUpView();
}
/**
軌迹消息
**/
private void sendPictureTxtMessage() {
// //建立一條文本消息
EMMessage message = EMMessage.createTxtSendMessage("商品消息已經發送到客服",
toChatUsername);
TrackMessageEntity msg=new TrackMessageEntity(25, channame, chanprice, "木床", chantu,"http://114.55.99.240/Commodity_Show.aspx?id="+chanid);
//發送json擴充
// JSONObject jsonMsgType = MessageHelper.getMessageExtFromPicture(witchCHAT);
JSONObject jsonMsgType=msg.getJSONObject();
if (jsonMsgType != null) {
// 給消息設定擴充
message.setAttribute("msgtype",jsonMsgType);
setUserAttibutes(message);
setSkillGroup(message);
sendMessage(message);
}
}
/**
* ---------------------------------------------------
* 設定自定義消息提供者
*
* @return
*/
public EaseCustomChatRowProvider onSetCustomChatRowProvider() {
return new CustomChatRowProvider();
}
/**
* 自定義實作ChatRow提供者
*/
class CustomChatRowProvider implements EaseCustomChatRowProvider {
/**
* 傳回自定義消息的個數(這個個數必須和你自定義的ChatRow界面個數一緻,否則會數組越界)
* FATAL EXCEPTION: main Process: com.easemob.easeui.customer, PID: 32217
* java.lang.ArrayIndexOutOfBoundsException: length=18; index=18
* at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6588)
*
* @return
*/
public int getCustomChatRowTypeCount() {
return 6;
}
/**
* 傳回消息的類型
*
* @param message
* @return
*/
public int getCustomChatRowType(EMMessage message) {
Log.v("--88--", "buju ");
if (message.getType() == EMMessage.Type.TXT) {
if (CustomHelper.getInstance().isCtrlTypeMessage(message)) {
return message.direct == EMMessage.Direct.RECEIVE ? 2 : 1;
}
else if (CustomHelper.getInstance().isTrackMessage(message)) {
return message.direct == EMMessage.Direct.RECEIVE ? 4 : 3;
}
else if (CustomHelper.getInstance().isOrderFormMessage(message)) {
return message.direct == EMMessage.Direct.RECEIVE ? 6 : 5;
}
}
return 0;
}
/**
* 傳回自定義消息的實作
*
* @param message
* @param position
* @param adapter
* @return
*/
public EaseChatRow getCustomChatRow(EMMessage message, int position, BaseAdapter adapter) {
if (message.getType() == EMMessage.Type.TXT) {
if (CustomHelper.getInstance().isCtrlTypeMessage(message)) {
CtrlTypeChatRow ctrlTypeChatRow = new CtrlTypeChatRow(mActivity, message, position, adapter);
ctrlTypeChatRow.setmChatRowListener(new MyChatRowListener());
return ctrlTypeChatRow;
}
else if (CustomHelper.getInstance().isTrackMessage(message)) {
return new TrackChatRow(getActivity(), message, position, adapter);
}
else if (CustomHelper.getInstance().isOrderFormMessage(message)) {
// return new OrderChatRow(mActivity, message, position, adapter);
}
}
return null;
}
}
@Override
public void onMessageBubbleLongClick(EMMessage message) {
// TODO Auto-generated method stub
}
/**
* ------------------------------------------------------------------
*
* 滿意度評價的回調接口,為了實作用戶端在滿意度ChatRow中發送滿意度類型的消息
*/
class MyChatRowListener implements CtrlTypeChatRow.CustomerChatRowListener {
@Override
public void onChatRowInteraction(EnquiryEntity enquiryEntity) {
EMMessage message = EMMessage.createTxtSendMessage("客服圖文混排消息", toChatUsername);
JSONObject jsonWeiChat = new JSONObject();
JSONObject jsonCtrlArgs = new JSONObject();
try {
jsonCtrlArgs.put("inviteId", enquiryEntity.getInviteId());
jsonCtrlArgs.put("serviceSessionId", enquiryEntity.getServiceSessionId());
jsonCtrlArgs.put("detail", enquiryEntity.getDetail());
jsonCtrlArgs.put("summary", enquiryEntity.getSummary());
jsonWeiChat.put("ctrlType", "enquiry");
jsonWeiChat.put("ctrlArgs", jsonCtrlArgs);
} catch (JSONException e) {
e.printStackTrace();
}
message.setAttribute(CustomConfig.C_ATTR_KEY_WEICHAT, jsonWeiChat);
sendMessage(message);
}
}
c、判斷客服消息的唯獨數量:
EMConversation conversation = EMChatManager.getInstance().getConversation(CustomConfig.DEFALUT_IM);
int chatnumint=conversation.getUnreadMsgCount();
環信客服的介入看似複雜,其實并不複雜
附:1、環信客服內建的ui架構,es上使用ui http://download.csdn.net/detail/heiya0409/9711950
2、在as上不使用官方ui,自己布局的bao http://download.csdn.net/detail/heiya0409/9711958