高级功能

创建消息和发送消息

在我们 SDK 中消息的载体是通过 SDK 中的 IMessage 类去实现的,我们发送消息给客服需要两个步骤,一是创建 IMessage 对象,二是发送 IMessage,在 V4.3.0 版本中我们开放了创建消息和发送消息的接口,App 可以通过 UnicornMessageBuilder 类中的方法去创建不同类型的 IMessage,然后通过 MessageService 中的方法去发送 IMessage。

自定义消息处理

在我们平时使用 ListView 和 RecyclerView 的时候可以根据数据源的不同使用不同的 item 布局,那我们 SDK 也是采取了这种方式去展示消息的,当 SDK 收到消息时,会根据 IMessage(消息的载体) 中不同的 Attachment(也就是我们所说的数据源) 展示不同的布局(每种布局都是一个 ViewHolder)。这只是 SDK 中展示消息的相关的处理逻辑,在 V4.3.0 版本中我们加入了用户自定义处理消息的接口,App 可以通过该接口去实现消息的处理,这里所说的处理和展示是完全分离的,SDK 在收到消息的时候,会通知 App 我收到消息,App 可以根据消息中的 attachment 类型去做不同的处理,但是这个处理不会影响我们 SDK 的展示工作,相关内容的示例代码如下:

//告诉 SDK 语音消息不需要展示 UI 了
MsgCustomizationRegistry.hideViewForMsgType(AudioAttachment.class);
//注册 handler 的工厂
MsgCustomizationRegistry.registerMessageHandlerFactory(new MessageHandlerFactory() {
    @Override
    public UnicornMessageHandler handlerOf(UnicornMessage message) {
        //下面是根据不同的消息进行不同的处理
        if(message.getAttachment() instanceof AudioAttachment){
            return new AudioDemoHandler();  //自定义处理音频消息的 Handler
        }else if(message.getAttachment() instanceof ImageAttachment){
            return new ImageDemoHandler();  //自定义的图片消息
        }else if(message.getAttachment() instanceof ProductAttachment){
            return new ProductDemoHandler(); //自定义商品消息
        }else {
            return null;  //不做处理
        }

    }
});

在这里 hideViewForMsgType 方法有必要说一下,当我们处理的消息不希望展示在客服界面的时候,可以使用该方法去隐藏。上面代码已经很清晰,在 SDK 初始化的时候,你可以注册一个 Factory,当 SDK 收到消息的时候回调用 handlerOf 方法,在 handlerOf 方法中可以根据不同的 attachment 返回不同的 handler,返回之后会调用 Handler 的 onMessage 方法,上面的三个 handler 都是需要 App 通过继承 UnicornMessageHandler 类去实现,下面的代码展示了如何自定义一个 handler:

public class DemoMsgHandler implements UnicornMessageHandler {
    /**
     *
     * @param context: 消息流所在的上下文
     * @param message:你将要处理的 message ,通过 message.getAttachment 可以拿到你注册的 attachment
     * @param handledOnce:之前是否已经成功处理过该消息,有些活动只需要处理一次,例如一条订单消息只需要弹一次订单列表,所以可以根据这个参数去判断是否需要处理
     * @return 是否处理了该消息,true 为处理了该消息,false 为没处理该消息
     */
    @Override
    public boolean onMessage(Context context, UnicornMessage message, boolean handledOnce) {
        //此方里面可以进行一些处理跳转界面,弹框等操作
        Intent intent = new Intent(context, OrderListActivity.class);
        context.startActivity(intent);
        return true;
    }
}

SDK 中事件的拦截处理

我们知道 SDK 中有很多事件,例如请求客服事件,请求访客评价事件等事件,在 V4.4.0 版本中我们开放了请求客服事件的拦截,App 可以通过实现 API 去监听 SDK 请求客服的事件并得到请求客服事件的一些数据,App 可以通过修改数据去干预请求客服的事件以达到请求特定客服的目的。下面的代码展示了如何去监听一个请求客服事件:

//一、首先在初始化的时候去赋值 YsfOption 中的 SDKEvents。然后去实现一个 EventProcessFactory 接口,每当有事件发生都会调用 eventOf 方法,可以根据事件的类型按需处理事件
YSFOptions options = new YSFOptions();
options.sdkEvents = new SDKEvents();
options.sdkEvents.eventProcessFactory = new EventProcessFactory() {
    @Override
    public UnicornEventBase eventOf(int eventType) {
        //0 代表请求客服事件
        if(eventType == 0){
            return new DemoRequestStaffEvent();
        } else if(eventType == 1){  //请求客服结果事件
            return new DemoConnectionResultEvent();
        } else if(eventType == 2) {   // 2 代表需要注册自定消息样式解析器的事件
NIMClient.getService(MsgService.class).registerCustomAttachmentParser(MsgTypeEnum.appCustom,DemoMsgParser.getInstance());
        }
        return null;
    }
};
//二、去自定义一个请求客服事件拦截器 DemoRequestStaffEvent,当有请求客服事件的时候,就会调用 onEvent 方法,然后在自定义一个 DemoConnectionResultEvent ,当客服连接有结果的时候同样会回调它的 onEvent 方法
/**
 * 请求客服事件的拦截器
 */
public class DemoRequestStaffEvent implements UnicornEventBase<RequestStaffEntry>{
    /**
     * 当事件发生的时候,会回调这个方法,在这个方法中,事件是处于等待的状态的只有调用了
     * callback 中的方法,事件才会继续
     * @param entry 请求客服事件的一些参数
     * @param context 当前界面的 context 对象,使用之前请判断是否为 null
     * @param callback SDK 的回调对象 具体请查看 EventCallback
     */
    @Override
    public void onEvent(final RequestStaffEntry entry, Context context, final EventCallback<RequestStaffEntry> callback) {
        if(context == null){
            ToastUtil.showLongToast("请求客服的信息为:" + entry.toString());
        }else {
            //当处理成功之后可以调用 onProcessEventSuccess 通知 sdk 可以请求客服了
            AlertDialog dialog = new AlertDialog.Builder(context).setMessage("请求客服的信息为" + entry.toString()+"是否拦截")
                    .setPositiveButton("是", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            callback.onInterceptEvent();
                        }
                    })
                    .setNegativeButton("否", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            callback.onProcessEventSuccess(entry);
                        }
                    }).create();
            dialog.show();
        }

    }
}


/**
 * Describe:客户连接结果事件通知的类
 */
public class DemoConnectionResultEvent implements UnicornEventBase<ConnectionStaffResultEntry> {

    /**
     * 当客服连接请求
     * @param connectionStaffResultEntry  请求客服结果事件的 entry
     * @param context 当前界面的 context 对象,使用之前请判断是否为 null
     * @param callback SDK 的回调对象  注意:如果该事件 SDK 不需要回调的时候,这个对象会为 null,所以当使用的时候需要做一下非null判断
     */
    @Override
    public void onEvent(ConnectionStaffResultEntry connectionStaffResultEntry, Context context, EventCallback<ConnectionStaffResultEntry> callback) {
        if(connectionStaffResultEntry.getCode() == 200){
            ToastUtil.showToast("请求客服成功,请求到的客服类型为:" + connectionStaffResultEntry.getStaffType());
        } else {
            ToastUtil.showToast("请求客服失败,失败的类型" + connectionStaffResultEntry.getCode());
        }
    }
}

供外部调用的事件

因为 APP 在使用客服功能的过程中,有可能想自己控制回话过程,所以 SDK 提供了相关方法供 APP 调用,具体方法都在 EventService.class 中,用户可自行查看

  1. 结束会话事件,在 V5.2.0 版本中,SDK 开放了结束会话的事件,用户可以通过该事件结束当前会话 具体操作请参考 SDK 中 EventService.closeSession 方法

  2. 调起评价界面,在 V5.2.0 版本中,SDK 开放了调起评价界面的接口,用户可以通过该接口打开评价的相关界面,具体请查看 SDK 中的 EventService.openEvaluation 方法

  3. 请求客服事件,当 APP 想主动请求客服的时候,可以通过 EventService.requestStaff 方法去请求客服,具体参数已经在方法中说明

  4. 退出排队事件,在 V5.4.0 版本中,当 APP 想自己弹窗实现退出排队的时候,可以通过 EventService.quitQueue 方法去退出排队

自定义本地消息

在 V4.7.0 版本中 SDK 开放了自定义本地消息的功能,开发者可以通过 SDK 开放的 API 实现任何样式的本地消息。具体实现涉及两块内容,一块是数据源(也就是 SDK 中的 MsgAttachment),一块是我们展示消息的 View (也就是 SDK 中的 UnicornMessageViewHolder),开发者可以通过实现 MsgAttachment 和 UnicornMessageViewHolder 来实现改功能,具体实现方案如下:

  1. 实现自己的 MsgAttachment ,数据源里面的数据开发者可以随意定义
  2. 实现自己的 UnicornMessageViewHolder,该类中主要涉及三个方法,getViewHolderResid (返回 item 的 layout布局),inflateFindView(进行 item 中的 findViewById 操作),bindContentView(数据绑定操作)。
  3. 通过监听 SDK 的事件,当 eventType = 2 的时候,去注册我们的数据源解析器,因为我们发送出去的消息都是一个 String 类型的消息,所以开发者需要在解析器中把这个 String 类型的消息解析成自己的 MsgAttachment。
  4. 通过 MsgCustomizationRegistry.registerMessageViewHolder 方法,将数据源和 viewHolder 联系起来。
  5. 通过 UnicornMessageBuilder.buildCustomMessage(开发者实现的 MsgAttachment) 方法去构建一条消息,然后通过 MessageService.sendMessage 去发送消息

具体实现基本就这 6 步,我们看一下每步实现的源码就会很清晰:

/**
 * 第一步实现自己的 MsgAttachment
 */
public class DemoAttachment implements MsgAttachment{

    public static final String TAG_STRING = "tag_string";
    public static final String TAG_URL = "tag_url";

    private String msgContent;

    private String imgUrl;

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }

    public String getMsgContent() {
        return msgContent;
    }

    public void setMsgContent(String msgContent) {
        this.msgContent = msgContent;
    }

    //这个方法必须重写,因为在存入数据库的时候需要用到
    @Override
    public String toJson(boolean send) {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put(TAG_STRING,getMsgContent());
            jsonObject.put(TAG_URL,getImgUrl());
        } catch (JSONException e) {
            //log
        }
        return jsonObject.toString();
    }

    @Override
    public boolean countToUnread() {
        return false;
    }

    @Override
    public String getContent(Context context) {
        return msgContent;
    }
}

/**
 * 第二步实现自己的 UnicornMessageViewHolder
 */
public class DemoCustomViewHolder extends UnicornMessageViewHolder{

    private TextView tvViewHolderDemoText;
    private TextView tvViewHolderDemoUrl;

    @Override
    public int getViewHolderResid() {
        return R.layout.viewholder_demo_custom;
    }

    @Override
    public void inflateFindView() {
        tvViewHolderDemoText = findViewById(R.id.tv_view_holder_demo_text);
        tvViewHolderDemoUrl = findViewById(R.id.tv_view_holder_demo_url);

    }

    @Override
    public void bindContentView(IMMessage message, Context context) {
        DemoAttachment attachment = (DemoAttachment) message.getAttachment();
        tvViewHolderDemoText.setText(attachment.getMsgContent());
        tvViewHolderDemoUrl.setText(attachment.getImgUrl());
        progressBar.setVisibility(View.GONE);
    }

    @Override
    protected boolean isMiddleItem() {
        return true;
    }

    @Override
    protected boolean showAvatar() {
        return false;
    }
}

/**
 * 第三步请见上文所述的拦截 SDK 事件一节
 */
 
/**
 * 第四步注册 Attachment 和 ViewHolder 的关系
 * 该步可以在任何地方调用,但切记在发送自定义消息之前
 */
MsgCustomizationRegistry.registerMessageViewHolder(DemoAttachment.class, DemoCustomViewHolder.class);

/**
 * 第五步创建消息并保存消息到本地
 */
IMMessage message = UnicornMessageBuilder.buildAppCustomMessage(attachment);
//具体参数请查看 MessageService 类的说明
MessageService.saveMessageToLocal(message, true, true);

自定义评价界面

在 V5.0.0 版本中我们开放了自定义评价界面的接口,整体的思路就是当用户点击评价或者客服邀请评价的时候,我们把评价事件交给 App 方处理,当 App 方处理之后调用 SDK 的接口回传评价结果就可以完成评价了,主要涉及的 SDK 接口有如下几个:

/**
 * Describe: 评价相关 Api class
 */
public class EvaluationApi {


    private OnEvaluationEventListener onEvaluationEventListener;

    /**
     * 评价事件监听类,用户可以通过调用 setOnEvaluationEventListener
     * 方法去设置自己实现的 OnEvaluationEventListener ,当用户设置了 OnEvaluationEventListener
     * 那么当用户触发了评价事件就会交给 App 方处理
     */
    public static abstract class OnEvaluationEventListener {
        /**
         * 评价状态改变
         *
         * @param state 0:不可评价,1:可评价,2:评价完成
         */
        public void onEvaluationStateChange(int state) {
        }

        /**
         * 邀评消息被点击,App 方可以在此方法启动自己的评价界面
         * @param entry 评价的相关数据
         */
        public void onEvaluationMessageClick(EvaluationOpenEntry entry, Context context) {
        }
    }

    private static class SingletonHolder {
        private static final EvaluationApi sInstance = new EvaluationApi();
    }

    public static EvaluationApi getInstance() {
        return SingletonHolder.sInstance;
    }


    public OnEvaluationEventListener getOnEvaluationEventListener() {
        return onEvaluationEventListener;
    }

    public void setOnEvaluationEventListener(OnEvaluationEventListener onEvaluationEventListener) {
        this.onEvaluationEventListener = onEvaluationEventListener;
    }

    /**
     * 评价
     *
     * @param shopCode 商家ID, 在 EvaluationOpenEntry 里面有这个值,只需要回传就可以了
     * @param sessionId 会话 ID ,在 EvaluationOpenEntry 里面有这个值,只需要回传就可以了
     * @param score    评分
     * @param remark   评价内容
     * @param tagList  标签
     * @param name     评价结果的文案,例如非常满意、满意、不满意等
     * @param callback 回调
     */
    public void evaluate(String shopCode, long sessionId, int score, String remark, List<String> tagList, String name, RequestCallbackWrapper<String> callback) {
        SessionManager.getInstance().getEvaluationManager().doEvaluate(shopCode, sessionId, score, remark, tagList, name, callback);
    }

}

当需要自定义评价界面的时候,App 首先需要实现一个自己的 OnEvaluationEventListener ,然后把实现的 OnEvaluationEventListener 赋值给 EvaluationApi 中的 onEvaluationEventListener 变量,例如如下代码:

EvaluationApi.getInstance().setOnEvaluationEventListener(new EvaluationApi.OnEvaluationEventListener() {
                @Override
                public void onEvaluationStateChange(int state) {
                    //当评价状态改变的时候,会调用该方法,所以当有需求的时候可以在改方法中做一些操作
                }

                @Override
                public void onEvaluationMessageClick(EvaluationOpenEntry entry, Context context) {
                    if (context == null)
                        return;
                    Intent intent = new Intent(context, DemoEvaluationActivity.class);
                    intent.putExtra(ENTRYTAG, entry);
                    context.startActivity(intent);
                }
            });

从上面的代码中看到,当调用 onEvaluationMessageClick 方法的时候我们跳转到了 DemoEvaluationActivity(也就是我们自定义的评价界面),然后把评价里面的数据给到了DemoEvaluationActivity,这样就可以在 DemoEvaluationActivity 展示评价的数据了。

如果想要查看评价里面的数据具体如何使用,请参考 Demo 工程中的 DemoEvaluationActivity 类

自定义商品类型消息

在 SDK V5.5.0 版本中新增了自定义商品类型的消息,主要的功能为当客服发送 iframe 商品消息的时候,SDK 可以自定义该类型消息的样式。

实现方式

一、在客服端需要发送如下格式的数据:

{
	"productCustomField":"2342342342342342",
	"isOpenCustomProduct":true,
	"sendByUser":1,
	"actionText":"fasonglianjiessz",
	"actionTextColor":"#FF1493",
	"template":"pictureLink",
	"picture": "https://ysf.nosdn.127.net/32FD7DAACD42CE79DD03B1B82F690B7B?imageView&type=png",
	"title": "[自营]Orange Juice",
	"desc": "Relish the goodness of hand-picked oranges from the finest orchards. Foster a healthy lifestyle with the benefits of oranges. 100 percent orange juice with no added sugar for a healthy you.",
	"price": "¥999",
	"url": "http://10.242.118.244:8000/shop-detail.html",
	"activity":"活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动",
	"activityHref": "https://www.baidu.com",
	"showCustomMsg":1
}

之前发送 iframe 商品消息是没有 productCustomField 和 isOpenCustomProduct 这两个字段的,当 isOpenCustomProduct 为 true 的时候,商品消息的样式为自定义的,当为 false 的时候,使用 SDK 中默认的商品样式。所以如果想要使用自定义商品样式,发送数据中 isOpenCustomProduct 必须为 true。productCustomField 字段的意思是自定义商品消息的数据信息,用户可以把想要展示的自定义数据放到该字段中,当 SDK 收到该字段,会将该字段透传到 CustomProductParserparseCustomProduct 方法中

二、定义 Attachment 和 ViewHolder

一条消息由两部分组成,展示的数据和展示的样式,下面分两步讲解自定义商品消息体的实现。

因为每一种类型的消息数据格式都是不同的,自定义商品消息也不例外,所以需要定义自定义商品消息的 Attachment ,Attachment 中的字段由开发者自定义,想要展示什么数据就定义什么字段,例如:

//Attachment 一定要继承 UnicornAttachmentBase
public class DemoCustomProductAttachment extends UnicornAttachmentBase {
	//想要展示的信息
    private String customProductString;
	
	// attach 字段可以通过 CustomProductParser 的 parseCustomProduct 的参数获取
	 public DemoCustomProductAttachment(String attach) {
        super(attach);
    }


    public String getCustomProductString() {
        return customProductString;
    }

    public void setCustomProductString(String customProductString) {
        this.customProductString = customProductString;
    }
}

上面的代码只是简单的把 attach 直接拿过来使用,现实情况,用户可以把 productCustomField 定义为一个 String 类型的 json,然后把 productCustomField 解析出来更多的字段使用

上面自定义消息想要展示的数据已经定义好了,下面需要定义一下自定义商品消息的消息体,也就是 ViewHolder,用户可以通过继承 UnicornMessageViewHolder 实现,例如:

public class DemoCustomProductHolder extends UnicornMessageViewHolder {
    private TextView tvDemoViewHolder;
	//返回消息样式的layout id
    @Override
    public int getViewHolderResId() {
        return R.layout.viewholder_product;
    }
	//初始化相关 view
    @Override
    public void inflateFindView() {
        tvDemoViewHolder = findViewById(R.id.tv_demo_view_holder);
    }
	//进行数据绑定
    @Override
    public void bindContentView(IMMessage message, Context context) {
        DemoCustomProductAttachment demoCustomProductAttachment = (DemoCustomProductAttachment) message.getAttachment();
        tvDemoViewHolder.setText(demoCustomProductAttachment.getCustomProductString());
    }
}

上面的代码中只是比较简单的,用户可以自行扩展布局,用户也可以查看 UnicornMessageViewHolder 中的相关方法修改消息的样式

三、定义解析器 Parser

因为当客服发送过来的消息是一个 String 类型的数据,所以需要把这个 String 解析成自定义商品消息对应的 Attachment,当用户需要解析自定义商品消息的时候 SDK 会去回调 CustomProductParserparseCustomProduct 方法,用户可以在 YSFOptionscustomProductParser 设置自己的 Parser,例如:

YSFOptions options = new YSFOptions();
options.customProductParser = DemoMsgParser.getInstance();

上面的代码中指定了 DemoMsgParser 为自定义商品消息的解析器,下面看一下里面的具体实现:

public class DemoMsgParser implements CustomProductParser {
	//单例对象
    private static DemoMsgParser instance;
	
    public static DemoMsgParser getInstance() {
        if (instance == null) {
            instance = new DemoMsgParser();
        }
        return instance;
    }

    private DemoMsgParser() {
    }
    //重写了自定义商品消息的解析方法,当有自定义商品消息的时候会回调该方法,用户需要把
    //解析完成 Attachment return 给 SDK
    @Override
    public UnicornAttachmentBase parseCustomProduct(String attach) {
        DemoCustomProductAttachment attachment = new DemoCustomProductAttachment(attach);
        try {
            JSONObject jsonObject = new JSONObject(attach);
            attachment.setCustomProductString(jsonObject.getString("productCustomField"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return attachment;
    }
}

上面的代码中实现了 CustomProductParser 接口,并实现了 parseCustomProduct 方法,该方法中我们我们通过解析 attach 字段,然后拿到了 productCustomField 字段(也就是客服发送过来的 productCustomField) 字段。客服端也可以把 productCustomField 里面塞一个 String 类型的 json 。这样 productCustomField 就可以解析出来更多的字段供展示使用。

四、注册 Attachment 和 ViewHolder 的对应关系

因为 SDK 需要把数据类型和 UI 对应起来,所以用户需要手动调用 MsgCustomizationRegistry.registerMessageViewHolder 方法告诉 SDK Attachment 和 ViewHolder 的对应关系(切记在 init 之后,打开客服界面之前调用),下面看代码:

MsgCustomizationRegistry.registerMessageViewHolder(DemoCustomProductAttachment.class, DemoCustomProductHolder.class);

上面展示了完成自定义商品消息的流程,如果有不理解的地方可以查看 Demo 中的源码和源码中的相关注释

人工自助提工单

在 V5.7.0 版本中,SDK 增加了自助提工单的功能,用户可以通过在后台配置快捷入口和 "+" 中配置提工单的功能,SDK 中也开放了主动调用工单功能的接口,主要涉及 SDK 中的以下内容:

1、WorkSheetAction

如果用户只是想在 “+” 中使用自助提工单的功能,那么可以在 ActionPanelOptions 的 ActionListProvider 中加入 WorkSheetAction,切记不要忘记给 WorkSheetAction 设置 templateId