七鱼开发文档中心
总览服务端 APIWebiOSAndroid鸿蒙Uniapp微信小程序插件三方接入

自定义事件

事件配置

本节说明事件回调能力的配置入口和整体使用方式。

为了让用户在使用七鱼 SDK 时拥有更大的灵活性,我们提供了事件自定义响应接口。

自定义响应接口在YSFOptions对象中,初始化YSFOptions对象。

YSFOptions options = new YSFOptions();

修改设置

SDK提供了修改自定义事件的代码,该方法是对象直接赋值,不是对之前的设置进行更新。

代码示例

Unicorn.updateOptions(YSFOptions options);

点击事件

本节说明链接、按钮、快捷入口等点击事件的拦截和自定义处理方式。

URL链接点击响应

接口描述

对于SDK中带有 URL 链接,SDK 将会解析出该链接。用户点击这个链接后,SDK 提供了一个配置选项,允许应用自己处理这个点击事件,用于在应用的内置浏览器中打开链接,以及做钓鱼网址过滤等场景。

参数说明

参数名类型参数说明取值说明
onMessageItemClickListenerOnMessageItemClickListenerURL点击事件响应处理默认不实现,如果没有设置,启动系统默认浏览器打开超链接

OnMessageItemClickListener

消息显示项中超链接的点击响应函数

接口回调函数说明
OnMessageItemClickListener需要实现onURLClicked方法

代码示例

OnMessageItemClickListener messageItemClickListener = new OnMessageItemClickListener() {
    // 响应 url 点击事件
    public void onURLClicked(Context context, String url) {
        // 打开内置浏览器等动作
    }
}
options.onMessageItemClickListener = messageItemClickListener;

URL链接机器人一触即达/快捷入口点击响应

接口描述

在机器人阶段,快捷入口或者服务直达中带有 URL 链接,SDK 将会解析出该链接。用户点击这个链接后, SDK 提供了一个配置选项,允许应用自己处理这个点击事件,用于在应用的内置浏览器中打开链接,以及做钓鱼网址过滤等场景。

参数说明

参数名类型参数说明取值说明
onBotEventListenerOnBotEventListener服务直达/机器人快捷入口URL链接点击响应默认不实现,如果没有设置,启动系统默认浏览器打开超链接

OnBotEventListener

服务直达/机器人快捷入口中超链接的点击响应函数

接口回调函数说明
OnBotEventListener需要实现onURLClicked方法

代码示例

OnBotEventListener onBotEventListener = new OnBotEventListener() {
    // 响应 url 点击事件
    public boolean onUrlClick(Context context, String url) {
        // 打开内置浏览器等动作
    }
}
options.onBotEventListener = onBotEventListener;

快捷按钮点击响应

接口描述

当客户在后台设置自定义快捷入口时,需要设置快捷按钮点击响应监听器

参数说明

参数名类型参数说明取值说明
quickEntryListenerQuickEntryListener快捷按钮点击监听器默认不实现,需要客户自己实现

QuickEntryListener

快捷按钮点击监听器

接口回调函数说明
QuickEntryListener需要实现onClick方法,onClick方法有中三个参数:Context context:上下文String shopId:平台id,非平台默认为-1QuickEntry quickEntry:快捷入口信息

QuickEntry

参数名类型参数说明
idlong入口ID
nameString入口名字
iconUrlString入口 icon url ,支持的 url 类型由 ImageLoader 决定

代码示例

QuickEntryListener quickEntryListener = new QuickEntryListener() {
	@Override
	public void onClick(Context context, String shopId, QuickEntry quickEntry) {
		ToastUtils.show("点击快捷入口" + quickEntry.getId());
	}
};
options.quickEntryListener = quickEntryListener;

客服事件

本节说明请求客服、连接结果、头像、权限等客服流程事件的处理方式。

自定义客服事件处理

接口描述

SDK 的客服事件集中配置在 SDKEvents 中,接入时先初始化该类。

SDKEvents sdkEvents = new SDKEvents();
options.sdkEvents = sdkEvents;

EventProcessFactory

SDKEvents.eventProcessFactory 的类型是 EventProcessFactory。接口回调中的 int 参数代表不同事件类型,返回值是 UnicornEventBase。

接口回调函数说明
EventProcessFactory需要实现eventOf方法,eventOf方法有中一个int值参数:0: 请求客服事件1: 连接客服结果事件2: 注册自定义消息解析器的事件
3: 退出客服界面事件(只适用于 Activity 接入方式)4: 点击头像事件
5: 申请权限事件

UnicornEventBase

事件处理的基类,可以通过继承该类完成对事件的拦截,泛型T表示存放该事件所需的数据。

接口回调函数说明
UnicornEventBase<T>该接口会有三个回调函数:事件处理的回调:void onEvent(T t, Context context, EventCallback<T> callback)拒绝事件的回调:default boolean onDenyEvent(Context context, T t)同意事件的回调:default void onGrantEvent(Context context, T t)

EventCallback

事件回调的基类,当 APP 处理完成想要处理的事件之后,需要调用该callback 的 onProcessEventSuccess 方法通知 sdk 处理成功,可以进行接下来的流程。

接口回调函数说明
EventCallback<T>该接口会有四个回调函数:处理成功的回调:void onProcessEventSuccess(T t)处理失败的回调:void onPorcessEventError()不处理事件的回调:void onNotPorcessEvent()拦截事件的回调:void onInterceptEvent()

代码示例

SDKEvents sdkEvents = new SDKEvents();
DemoRequestStaffEvent demoRequestStaffEvent = new DemoRequestStaffEvent();
DemoConnectionResultEvent demoConnectionResultEvent = new DemoConnectionResultEvent();
DemoLeaveActivityEvent demoLeaveActivityEvent = new DemoLeaveActivityEvent();
DemoAvatarClickEvent demoAvatarClickEvent = new DemoAvatarClickEvent();
DemoRequestPermissionEvent demoRequestPermissionEvent = new DemoRequestPermissionEvent();
sdkEvents.eventProcessFactory = new EventProcessFactory() {
    @Override
    public UnicornEventBase eventOf(int eventType) {
        //0:请求客服事件
        //1:连接客服结果事件
        //2:注册自定义消息解析器的事件
        //3: 退出客服界面事件(fragment 的集成方式不会回调该事件)
        //4: 点击头像事件
        //5: 申请权限事件
        Log.i("YsfDemoApplication", "eventType:" + eventType);
        if (eventType == 0) {
            // return demoRequestStaffEvent;
        } else if (eventType == 1) {
            // return demoConnectionResultEvent;
        } else if (eventType == 2) {
            NIMClient.getService(YsfService.class).registerAttachmentParser(MsgTypeEnum.appCustom.getValue(), DemoMsgParser.getInstance());
        } else if (eventType == 3) { // 退出客服界面事件
           // return demoLeaveActivityEvent;
        } else if (eventType == 4) {
            return demoAvatarClickEvent;
        } else if (eventType == 5) {
            return demoRequestPermissionEvent;  // 为防止反复创建对象,这里直接返回一个对象
        }
        return null;
    }
};

连接客服结果通知事件

请求客服事件结果的通知类,在这里可以发送消息等事件。

连接客服结果code类型说明
201int没有客服
202int请求失败
203int排队中
204int长连接失效
205int没有客服在线,留言关闭

代码示例

public class DemoConnectionResultEvent implements UnicornEventBase<ConnectionStaffResultEntry> {
    @Override
    public void onEvent(ConnectionStaffResultEntry connectionStaffResultEntry, Context context, EventCallback<ConnectionStaffResultEntry> callback) {
        if(connectionStaffResultEntry.getCode() == 200){
            Toast.makeText(context, "请求客服成功,请求到的客服类型为:" + connectionStaffResultEntry.getStaffType(), Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "请求客服失败,失败的类型" + connectionStaffResultEntry.getCode(), Toast.LENGTH_SHORT).show();
        }
    }
}

注册自定义消息解析器的事件

如果客户想要自定义消息及展示,可以在这里注册自定义消息拦截

代码示例

NIMClient.getService(YsfService.class).registerAttachmentParser(MsgTypeEnum.appCustom.getValue(), DemoMsgParser.getInstance());

退出客服界面事件(只适用于 Activity 接入方式)

如果想要咨询界面返回事件的监听,当用户从咨询界面退出的时候会回调相关方法通知 APP 。

代码示例

public class DemoLeaveActivityEvent implements UnicornEventBase<String> {

    @Override
    public void onEvent(String s, final Context context, EventCallback<String> callback) {
        AlertDialog dialog = new AlertDialog.Builder(context)
                .setMessage("是否退出会话界面?")
                .setPositiveButton("是", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (context instanceof Activity) {
                            ((Activity) context).finish();
                        }
                    }
                })
                .setNegativeButton("否", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                }).create();
        dialog.show();
    }
}

点击头像事件

SDK提供了头像点击事件。

代码示例

public class DemoAvatarClickEvent implements UnicornEventBase<ClickAvatarEntry> {
    @Override
    public void onEvent(ClickAvatarEntry clickAvatarEntry, Context context, EventCallback<ClickAvatarEntry> callback) {
        if (clickAvatarEntry.isStaff()) {
            // 点击的是客服头像
            Toast.makeText(context, "点击的是客服头像", Toast.LENGTH_SHORT).show();
        } else {
            // 点击的是用户头像
            Toast.makeText(context, "点击的是用户头像", Toast.LENGTH_SHORT).show();
        }
    }
}

申请权限事件

由于隐私问题,SDK 开放了申请权限的事件,当 SDK 申请相关权限的时候,会通过 App 并把申请权限的场景和申请权限的列表给到 App。

参数名类型参数说明
requestPermissionEventEntryRequestPermissionEventEntry获取权限相关的类

RequestPermissionEventEntry的方法

// 申请权限的场景
public int getScenesType();
// 申请的权限列表
public List<String> getPermissionList();

权限场景常量

常量值场景说明
SCENES_SELECT_MEDIA0选择媒体从本地选择图片或视频
SCENES_TAKE_VIDEO1拍摄视频启动视频拍摄
SCENES_SAVE_IMAGE2保存图片保存图片到本地
SCENES_SAVE_VIDEO3保存视频保存视频到本地
SCENES_SELECT_VIDEO4选择视频从本地选择视频
SCENES_SELECT_FILE5选择文件从本地选择文件
SCENES_SELECT_IMAGE6选择图片从本地选择图片
SCENES_TAKE_PHOTO7拍照启动相机拍照
SCENES_TAKE_AUDIO8录音录制语音消息
SCENES_VIDEO_CHAT9视频客服进入视频客服相关能力
SCENES_POST_NOTIFICATIONS10通知栏权限申请 Android 通知栏权限

代码示例

public class DemoRequestPermissionEvent implements UnicornEventBase<RequestPermissionEventEntry> {
    private Map<String, String> permissionMap = new HashMap<>();

    private boolean showNotificationPermission = true;
    private AlertDialog requestPermissionDialog;

    public DemoRequestPermissionEvent() {
        permissionMap.put("android.permission.RECORD_AUDIO", "麦克风");
        permissionMap.put("android.permission.CAMERA", "相机");
        permissionMap.put("android.permission.READ_EXTERNAL_STORAGE", "存储");
        permissionMap.put("android.permission.WRITE_EXTERNAL_STORAGE", "存储");
        permissionMap.put("android.permission.READ_MEDIA_AUDIO", "多媒体文件");
        permissionMap.put("android.permission.READ_MEDIA_IMAGES", "多媒体文件");
        permissionMap.put("android.permission.READ_MEDIA_VIDEO", "多媒体文件");
        permissionMap.put("android.permission.POST_NOTIFICATIONS", "通知栏权限");
        permissionMap.put("android.permission.READ_MEDIA_VISUAL_USER_SELECTED", "照片和视频进行选择性授权");
    }

    private String transToPermissionStr(List<String> permissionList) {
        if (permissionList == null || permissionList.size() == 0) {
            return "";
        }
        HashSet<String> set = new HashSet<>();
        for (int i = 0; i < permissionList.size(); i++) {
            if (!TextUtils.isEmpty(permissionMap.get(permissionList.get(i)))) {
                set.add(permissionMap.get(permissionList.get(i)));
            }
        }
        if (set.isEmpty()) {
            return "";
        }
        StringBuilder str = new StringBuilder();
        for (String temp : set) {
            str.append(temp);
            str.append("、");
        }
        if (str.length() > 0) {
            str.deleteCharAt(str.length() - 1);
        }
        return str.toString();
    }

    /**
     * 该方法为点击相应的权限场景,用户可以通过RequestPermissionEventEntry.getPermissionList()拿到相应的权限,根据
     * 自己APP的权限规则,作自己的处理.
     * <p>
     * 比如判断客户之前点击的是拒绝权限还是不再询问,可以使用 AppCompatActivity.shouldShowRequestPermissionRationale()
     * 方法等各种情况都是在这个回调中进行自己的处理.
     * <p>
     * 各种情况都处理完了,可以告诉SDK,是要申请权限还是拒绝,调用SDK相应的方法.
     * callback.onProcessEventSuccess(requestPermissionEventEntry):用户同意授予权限
     * callback.onInterceptEvent():用户不授予权限,SDK自己处理不授予权限的提醒;或者用户自己处理不授予权限的提醒,就不要调用这个方法了
     *
     * @param requestPermissionEventEntry 获取权限相关的类
     * @param context                     当前界面的 context 对象,使用之前请判断是否为 null
     * @param callback                    sdk 的回调对象  注意:如果该事件 sdk 不需要回调的时候,这个对象会为 null,所以当使用的时候需要做一下非null判断
     */
    @Override
    public void onEvent(RequestPermissionEventEntry requestPermissionEventEntry, Context context, EventCallback<RequestPermissionEventEntry> callback) {

        //申请权限的场景
        //从本地选择媒体文件(视频和图片):0
        //拍摄视频场景:1
        //保存图片到本地:2
        //保存视频到本地:3
        //选择本地视频:4
        //选择本地文件:5
        //选择本地图片:6
        //拍照:7
        //录音:8
        //视频客服:9
        //通知栏权限:10
        if (context instanceof Activity) {
            if (((Activity) context).isFinishing()) {
                return;
            }
        } else if (context instanceof ContextWrapper) {
            if (((ContextWrapper) context).getBaseContext() instanceof Activity) {
                if (((Activity) ((ContextWrapper) context).getBaseContext()).isFinishing()) {
                    return;
                }
            }
        }
        int type = requestPermissionEventEntry.getScenesType();
        if (type == 10) {
            // 适配通知栏权限,自己去处理,这里只是一个示例
            // 因为通知栏权限如果没有给,这里会反复被触发,因为使用一个标志位来控制
            if (!showNotificationPermission) {
                return;
            }
            showNotificationPermission = false;
            // 如果没有适配通知栏权限,可以在这里处理,延迟500ms
            new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (InitHandler.getCurrentActivity() != null) {
                        showDialog(requestPermissionEventEntry, InitHandler.getCurrentActivity(), callback, type);
                    }
                }
            }, 500);
            return;
        }
        showDialog(requestPermissionEventEntry, context, callback, type);
    }

    private void showDialog(RequestPermissionEventEntry requestPermissionEventEntry, Context context, EventCallback<RequestPermissionEventEntry> callback, int type) {
        String permissionName = transToPermissionStr(requestPermissionEventEntry.getPermissionList());
        requestPermissionDialog = new AlertDialog.Builder(context)
                .setMessage("为保证您" + type + "功能的正常使用," + "需要使用您的:" + (TextUtils.isEmpty(permissionName) ? "相关" : permissionName) + "权限,\n" + "拒绝或取消不影响使用其他服务")
                .setCancelable(false)
                .create();
        // 获取 Window 对象并设置位置
        Window window = requestPermissionDialog.getWindow();
        if (window != null) {
            window.setGravity(Gravity.TOP); // 设置对话框在顶部
            window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        requestPermissionDialog.setCanceledOnTouchOutside(false);
        requestPermissionDialog.show();
        //如果想用户授予权限,需要调用 onProcessEventSuccess 告诉 SDK 处理成功
        callback.onProcessEventSuccess(requestPermissionEventEntry);
    }


    /**
     * 当相关权限被拒绝后,客户自己的处理
     *
     * @return 返回值默认为false,若返回值为true,则客户自己处理
     */
    @Override
    public boolean onDenyEvent(Context context, RequestPermissionEventEntry requestPermissionEventEntry) {
        String permissionName = transToPermissionStr(requestPermissionEventEntry.getPermissionList());
        AlertDialog dialog = new AlertDialog.Builder(context).setMessage("您没有:" + (TextUtils.isEmpty(permissionName) ? "相关" : permissionName) + "权限,\n" + "是否进行其他设置")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //用户自定义拒绝后的权限处理
                        Toast.makeText(context, "我自己处理没有的权限情况", Toast.LENGTH_SHORT).show();
                        dismissRequestPermissionDialog();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(context, "我什么也没有做", Toast.LENGTH_SHORT).show();
                        dismissRequestPermissionDialog();
                    }
                }).create();
        dialog.show();
        return true;
    }

    /**
     * 当相关权限被授予后的回调
     * @param context                     当前界面的 context 对象,使用之前请判断是否为 null
     * @param requestPermissionEventEntry 获取权限相关的类
     */
    @Override
    public void onGrantEvent(Context context, RequestPermissionEventEntry requestPermissionEventEntry) {
        dismissRequestPermissionDialog();
    }

    private void dismissRequestPermissionDialog() {
        if (requestPermissionDialog != null && requestPermissionDialog.isShowing()) {
            requestPermissionDialog.dismiss();
        }
    }
}

转发系统权限申请结果

从 Android SDK 11.8.0 开始,SDK 提供 UnicornPermissionHelper.onRequestPermissionsResult()。宿主 Activity 收到系统权限申请结果后,应将结果转发给 SDK,以便 SDK 继续执行对应的拍照、录音或媒体选择流程。

@Override
public void onRequestPermissionsResult(
        int requestCode,
        @NonNull String[] permissions,
        @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    UnicornPermissionHelper.onRequestPermissionsResult(
            this, requestCode, permissions, grantResults);
}

方法签名如下:

public static void onRequestPermissionsResult(
        @NonNull Context context,
        int requestCode,
        @NonNull String[] permissions,
        @NonNull int[] grantResults)

视频与文件事件

本节说明视频客服、图片、视频和文件选择等媒体相关事件的处理方式。

手动启动 SDK 选择文件界面

SDK增加了选择文件的功能,用户可以通过后台设置,或者通过配置InputPanelOptions 中的 getActionList 方法返回 PickFileAction 添加选择文件的功能。 用户也可以自己调用选择文件的界面,然后再把选择之后的数据回传给 SDK 就可以了

代码示例

第一步:需要启动文件选择时,调用 UnicornPickFileHelper.goPickFileActivity()。

UnicornPickFileHelper.goPickFileActivity(this, makeRequestCode(RequestCode.SELECT_FILE_REQUEST_CODE));

第二步: 在 Activity 的 onActivityResult 方法中将 data 回传给 SDK

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case RequestCode.SELECT_FILE_REQUEST_CODE:
            if (resultCode == RESULT_OK) {
                UnicornPickFileHelper.onPickFileResult(this, data);
            }
            break;
    }

}

上面两步就可以完成文件选择的功能了。

这篇文档有帮助吗?

目录