自定义事件
事件配置
本节说明事件回调能力的配置入口和整体使用方式。
为了让用户在使用七鱼 SDK 时拥有更大的灵活性,我们提供了事件自定义响应接口。
自定义响应接口在YSFOptions对象中,初始化YSFOptions对象。
YSFOptions options = new YSFOptions();修改设置
SDK提供了修改自定义事件的代码,该方法是对象直接赋值,不是对之前的设置进行更新。
代码示例
Unicorn.updateOptions(YSFOptions options);点击事件
本节说明链接、按钮、快捷入口等点击事件的拦截和自定义处理方式。
URL链接点击响应
接口描述
对于SDK中带有 URL 链接,SDK 将会解析出该链接。用户点击这个链接后,SDK 提供了一个配置选项,允许应用自己处理这个点击事件,用于在应用的内置浏览器中打开链接,以及做钓鱼网址过滤等场景。
参数说明
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| onMessageItemClickListener | OnMessageItemClickListener | URL点击事件响应处理 | 默认不实现,如果没有设置,启动系统默认浏览器打开超链接 |
OnMessageItemClickListener
消息显示项中超链接的点击响应函数
| 接口回调函数 | 说明 |
|---|---|
| OnMessageItemClickListener | 需要实现onURLClicked方法 |
代码示例
OnMessageItemClickListener messageItemClickListener = new OnMessageItemClickListener() {
// 响应 url 点击事件
public void onURLClicked(Context context, String url) {
// 打开内置浏览器等动作
}
}
options.onMessageItemClickListener = messageItemClickListener;URL链接机器人一触即达/快捷入口点击响应
接口描述
在机器人阶段,快捷入口或者服务直达中带有 URL 链接,SDK 将会解析出该链接。用户点击这个链接后, SDK 提供了一个配置选项,允许应用自己处理这个点击事件,用于在应用的内置浏览器中打开链接,以及做钓鱼网址过滤等场景。
参数说明
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| onBotEventListener | OnBotEventListener | 服务直达/机器人快捷入口URL链接点击响应 | 默认不实现,如果没有设置,启动系统默认浏览器打开超链接 |
OnBotEventListener
服务直达/机器人快捷入口中超链接的点击响应函数
| 接口回调函数 | 说明 |
|---|---|
| OnBotEventListener | 需要实现onURLClicked方法 |
代码示例
OnBotEventListener onBotEventListener = new OnBotEventListener() {
// 响应 url 点击事件
public boolean onUrlClick(Context context, String url) {
// 打开内置浏览器等动作
}
}
options.onBotEventListener = onBotEventListener;快捷按钮点击响应
接口描述
当客户在后台设置自定义快捷入口时,需要设置快捷按钮点击响应监听器
参数说明
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| quickEntryListener | QuickEntryListener | 快捷按钮点击监听器 | 默认不实现,需要客户自己实现 |
QuickEntryListener
快捷按钮点击监听器
| 接口回调函数 | 说明 |
|---|---|
| QuickEntryListener | 需要实现onClick方法,onClick方法有中三个参数:Context context:上下文String shopId:平台id,非平台默认为-1QuickEntry quickEntry:快捷入口信息 |
QuickEntry
| 参数名 | 类型 | 参数说明 |
|---|---|---|
| id | long | 入口ID |
| name | String | 入口名字 |
| iconUrl | String | 入口 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 | 类型 | 说明 |
|---|---|---|
| 201 | int | 没有客服 |
| 202 | int | 请求失败 |
| 203 | int | 排队中 |
| 204 | int | 长连接失效 |
| 205 | int | 没有客服在线,留言关闭 |
代码示例
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。
| 参数名 | 类型 | 参数说明 |
|---|---|---|
| requestPermissionEventEntry | RequestPermissionEventEntry | 获取权限相关的类 |
RequestPermissionEventEntry的方法
// 申请权限的场景
public int getScenesType();
// 申请的权限列表
public List<String> getPermissionList();权限场景常量
| 常量 | 值 | 场景 | 说明 |
|---|---|---|---|
| SCENES_SELECT_MEDIA | 0 | 选择媒体 | 从本地选择图片或视频 |
| SCENES_TAKE_VIDEO | 1 | 拍摄视频 | 启动视频拍摄 |
| SCENES_SAVE_IMAGE | 2 | 保存图片 | 保存图片到本地 |
| SCENES_SAVE_VIDEO | 3 | 保存视频 | 保存视频到本地 |
| SCENES_SELECT_VIDEO | 4 | 选择视频 | 从本地选择视频 |
| SCENES_SELECT_FILE | 5 | 选择文件 | 从本地选择文件 |
| SCENES_SELECT_IMAGE | 6 | 选择图片 | 从本地选择图片 |
| SCENES_TAKE_PHOTO | 7 | 拍照 | 启动相机拍照 |
| SCENES_TAKE_AUDIO | 8 | 录音 | 录制语音消息 |
| SCENES_VIDEO_CHAT | 9 | 视频客服 | 进入视频客服相关能力 |
| SCENES_POST_NOTIFICATIONS | 10 | 通知栏权限 | 申请 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;
}
}上面两步就可以完成文件选择的功能了。
这篇文档有帮助吗?