商城中基本上都添加了客服的功能,是买家和买家更方便的沟通。下面介绍的第三方即时通讯是环信。在客服这一模块中我们要做的就是,实现客服与买家之间的交流(包括发送文字,表情,图片,语音,文档,地理位置等),发送订单消息,商品信息等,实现二者之间的互动,买家给客服打分。补充就是六天纪录的删除,发送信息的时间,接接收消息的提醒等。
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做一些全局工作
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