# 功能配置
# 自定义客服分配
在打开客服咨询窗口或者构造 ServiceMessageFragment
时,接口中有一个参数是 ConsultSource
,通过该参数,我们可以对分配客服的流程做一些自定义操作。
# 分配客服&客服组
在 2.0 之前的版本中,请求客服时会根据企业在管理后台上对 App 客服分配的设置,来决定首先接入机器人还是人工客服,如果管理后台开启了机器人,则首先会接入机器人。如果管理后台也允许接入人工客服,那么用户可以通过回复 'RG' 或者 '人工客服' 一类的关键字切换到人工客服,界面上也能展现一个人工客服的入口,用户点击此入口,也能接入人工客服。
在 2.0 版本中,七鱼加入了访客分配的逻辑。如果 App 端开启了访客分配,那么当切换到人工客服时,会首先给出一个客服分组选择的入口,用户可以自主选择某个客服或者客服分组咨询。
在 2.2 版本中,SDK 在
ConsultSource
增加了指定客服或者客服组的参数,开发者可以在访客进入咨询界面前就指定好要为其服务的客服,例如从订单页面打开咨询界面时为其指定售后客服,在商品页面打开时,为其指定售前客服。在ConsultSource
中,staffId
为客服 ID,groupId
为客服组 ID,其值可以在管理后台设置页面的「访客分配」页面中找到。如果这两个参数都没有设置,则按照2.0版本的逻辑分配客服,如果指定了其中一个,则只会分配指定的客服或者在指定的客服分组中分配,如果指定的客服不在线,或者指定的客服分组中没有客服在线,则会提示用户客服不在线。如果同时指定staffId
和groupId
,以staffId
为准,忽略groupId
。机器人优先
在 3.1 版本中,SDK 又在 ConsultSource
中增加了 robotFirst 参数, 如果 robotFirst 为 true
,则会先由机器人接待。
- 配置常见问题模板
在 3.1 版本中,为了提高机器人的效率,在 ConsultSource
中还增加了 faqGroupId 参数。 faq 由客服人员在管理后台配置。如果指定了此参数,且请求客服为机器人客服,则会下发该 ID 对应的热门问题列表。
- 配置分流客服组 ID
在 V5.13.0 版本中,SDK 增加了配置分流客服组 ID 的功能,在 ConsultSource
配置 groupTmpId 参数即可。开发者可以通过配置该字段指定客服分流不同的模版。具体模版 ID 的值可以通过网页端客服工作台管理端 -> 在线系统 -> 设置 -> 会话流程 -> 会话分配 -> App 分配内容区域查询
- 配置机器人欢迎语
开发者可以通过配置 ConsultSource
的 robotWelcomeMsgId 字段来完成机器人不同欢迎语的配置。
# 分配机器人
在 3.12 版本中,为了满足企业不同业务,在 ConsultSource
中增加了 robotId 参数,用于指定特定的机器人客服。机器人 ID 可以在管理后台 - 设置 - App接入 中查看。
# 商品卡片
在打开咨询窗口时,还可以带上用户当前正在浏览的商品或订单信息。在 ConsultSource
中,设置字段为 productDetail
,其类型为 ProductDetail
。各字段通过 ProductDetail.Builder
设置,可以设置的信息有:
字段 | 意义 | 备注 |
---|---|---|
title | 商品标题 | 长度限制为 100 字符,超过自动截断。 |
desc | 商品详细描述信息。 | 长度限制为 300 字符,超过自动截断。 |
note | 商品备注信息(价格,套餐等) | 长度限制为 100 字符,超过自动截断。 |
picture | 缩略图图片的 url。 | 该 url 需要没有跨域访问限制,否则在客服端会无法显示。 长度限制为 1000 字符, 超长不会自动截断,但会发送失败。 |
url | 商品信息详情页 url。 | 长度限制为 1000 字符,超长不会自动截断,但会发送失败。 |
show | 是否在访客端显示商品消息。 | 默认为0,即客服能看到此消息,但访客看不到,也不知道该消息已发送给客服。 |
alwaysSend | 在会话开始后,仍可以发送该商品字段 | 默认为 false,不发送。 |
tags | 展示在客服端的一些可操作入口 | 默认为空,每个 tag 在客服端将展示为一个按钮. |
openTemplate | 是否开启商品的最新展示样式 | 默认不开启,当开启的时候,发送的商品只展示配置的图片 |
sendByUser | 是否需要用户手动发送 | 默认为false,当为 true 的时候,商品的下面讲出现一个按钮,用户可以点击该按钮发送商品 |
actionText | 手动发送按钮的文本 | 默认文本为发送链接 |
actionTextColor | 手动发送按钮文本的颜色 | 十六进制颜色例如:0xFFDB7093 |
isOpenReselect | 是否显示重新选择按钮 | 默认为 false,不显示。 |
reselectText | 重新选择按钮的文案 | 如果不设置,默认为"重新选择" |
handlerTag | 当点击重新选择按钮的时候,会将此字段回传 | 默认为 null |
productReslectOnclickListener | 商品重新选择的点击事件的回调,类似于我们普通的 OnClickListener | 无默认实现 |
cardType | 卡片类型,共三种类型。商品: 0,订单: 1,自定义: 2 | 默认为0,推荐必填 |
goodsCId | 商品类目ID | 长度限制为100字符,填写后有助于机器人识别商品和后续业务 |
goodsCName | 商品类目名称 | 长度限制为10字,如生鲜食品 |
goodsId | 商品ID | 长度限制为100字符,商品唯一标识符,填写后有助于机器人识别商品和后续业务,当卡片类型为商品时,推荐必填 |
orderId | 订单ID | 交易订单号(父订单的交易编号),当卡片类型为订单时,推荐必填 |
intent | 商品卡片意图 | 无默认实现 |
在3.1.0版本之前,商品信息只有在连上客服时发送一次,此后如果没有重新连接客服,无论商品信息是否改变,都不会再继续发送了。从3.1.0版本开始,开发者可以通过 alwaysSend
字段控制是否需要在中途发送新的商品信息。注意,如果上一次发送的商品链接和这一次的相同,后面一次的不会再发送给客服。在 V4.4.0 版本中,可以通过设置 ConsultSource
中的 isSendProductonRobot
去控制是否在进入机器人会话的时候发送商品
App 可以通过调用 MessageService.sendProductMessage(productDetail);
去在任何时刻发送商品
# 设置用户VIP等级
在 3.5 版本中,七鱼客服增加了设置用户 VIP 等级的功能,便于区分用户等级,允许 VIP 用户优先进线或为 VIP 用户指定专线客服,提升用户体验。
使用该功能需要在七鱼管理系统 -> 设置 -> VIP 客户设置页面打开 VIP 开关,否则设置将不会生效。
设置用户 VIP 等级需要为 ConsultSource
中的 vipLevel
字段赋值:
- 如果为 0,为普通用户;
- 如果为 1-10,为用户 VIP 等级 1-10 级;
- 如果为 11,为通用 VIP 用户,即不显示用户等级。
# 控制会话过程
SDK 增加了开发者对于会话过程控制的力度,允许用户主动结束会话和主动退出排队,允许用户退出聊天界面时弹出结束会话弹框和弹出满意度评价。
设置是否允许用户主动结束会话,设置该选项后,在会话状态下,右上角会有结束会话的入口。
SessionLifeCycleOptions setCanCloseSession(boolean canCloseSession);
设置是否允许用户主动退出排队,设置该选项后,在排队状态下,右上角会有退出排队的入口。如果用户通过按返回键退出,会给出弹框提示。
SessionLifeCycleOptions setCanQuitQueue(boolean canQuitQueue);
设置排队状态下,按返回键时给用户的提示语,该选项只有在setCanQuitQueue(boolean)
设置为 true 时才有效。
SessionLifeCycleOptions setQuitQueuePrompt(String quitQueuePrompt);
设置是否允许用户退出聊天界面时弹出结束会话弹框
SessionLifeCycleOptions setCanBackPrompt(boolean canBackPrompt);
用户返回时弹出结束会话弹框时设置是否弹出满意度评价,该选项只有在setCanBackPrompt(boolean)
设置为 true 时才有效。
SessionLifeCycleOptions setCanShowEvaluation(boolean canShowEvaluation);
控制会话过程的设置类是 SessionLifeCycleOptions
,可以在开始会话前设置给 ConsultSource
的 sessionLifeCycleOptions
字段。示例代码如下:
ConsultSource source = new ConsultSource(null, "自定义入口", null);
SessionLifeCycleOptions lifeCycleOptions = new SessionLifeCycleOptions();
lifeCycleOptions.setCanCloseSession(boolean)
.setCanQuitQueue(boolean)
.setQuitQueuePrompt(String)
.setCanBackPrompt(boolean)
.setCanShowEvaluation(boolean);
source.sessionLifeCycleOptions = lifeCycleOptions;
Unicorn.openServiceActivity(context, title, source);
如果使用 fragment 集成,则还需要对SessionLifeCycleOptions
的sessionLifeCycleListener
字段赋值,配置会话界面退出监听器
,说明如下:
/**
* 会话界面退出监听器<br>
* 仅在使用 fragment 集成时需要处理,如果使用 Activity 集成则无需处理。<br>
* 如果需要在用户退出排队时给予挽留提示,则需要在宿主 activity 触发 onBackPressed 时调用 ServiceMessageFragment 的 onBackPressed 方法,
* 只有当 fragment 的 onBackPressed 方法返回为 false 时,才能调用 Activity 的 onBackPressed。示例代码<br>
* <pre>
* public class ServiceMessageActivity extends Activity {
* public void onBackPressed() {
* if (!messageFragment.onBackPressed()) {
* super.onBackPressed();
* }
* }
* }
* </pre>
* 如果给予提示后用户仍然选择退出排队,则会回调{@link #onLeaveSession()},开发者需要在回调中关闭 fragment 。
*/
public interface SessionLifeCycleListener {
/**
* 用户主动离开客服咨询界面,此时需要关闭 fragment 。<br>
*/
void onLeaveSession();
}
# 设置客服信息
在 SDK 的 V4.6.0 的版本中,我们增加了会话中客服信息自定义的功能,App 方可以在进入客服界面之前进行设置客服信息,在进入会话之后,本次会话将展示设置的内容,设置方法为如下代码:
ConsultSource source = new ConsultSource(null, "自定义入口", null);
source.vipStaffid = "自定义的 vip staff id";
source.prompt = "连接客服成功的提示语";
source.VIPStaffAvatarUrl = "头像的 url";
source.vipStaffName = "客服的的名字";
source.vipStaffWelcomeMsg = "客服的欢迎语";
Unicorn.openServiceActivity(this, "网易七鱼测试", source);
# 输入栏快捷入口
在 3.13 版本中,七鱼 SDK 新增人工客服模式下的快捷入口,可用于用户选择并发送订单给客服。
快捷入口位于输入框上方,仅在人工客服和留言状态下显示。如果需要展示快捷入口,需要按照以下方法配置:
- 为
ConsultSource
中的quickEntryList
赋值,添加快捷入口
ConsultSource source = new ConsultSource(uri, title, custom);
...
source.quickEntryList = new ArrayList<>();
source.quickEntryList.add(new QuickEntry(0, "查订单", iconUrl));
source.quickEntryList.add(new QuickEntry(1, "查物流", iconUrl));
- 为
YSFOptions
中的quickEntryListener
赋值,用于监听快捷入口点击
YSFOptions options = new YSFOptions();
...
options.quickEntryListener = new QuickEntryListener() {
@Override
public void onClick(Context context, String shopId, QuickEntry quickEntry) {
ToastUtils.show("点击快捷入口" + quickEntry.getId());
if (quickEntry.getId() == 0) {
// 这里可根据 QuickEntry 做出相应的相应,如打开订单选择窗口
}
}
};
如果需要在用户选择订单后将商品信息发送给客服,需要调用以下接口
// 普通企业
MessageService.sendProductMessage(productDetail);
// 平台企业
POPManager.sendProductMessage(shopId, ProductDetail);
当使用这种方法发送商品的时候,手动发送相关配置参数不再生效,openTemplate 参数不会生效。在 V4.3.0 版本我们对发送商品接口进行了改造,老版本中 ProductDetail 中的show 参数是不生效的,改造之后 show 参数开始生效
# 设置允许在当前客服界面进入新客服界面
在 V5.12.0 版本中我们在 ConsultSource 中增加了 forbidUseCleanTopStart 字段,该字段的作用为是否使用 CLEAR_TOP 的方式启动客服界面,当用户想要在当前客服界面中进入新的客服界面时,需要把该字段设置为 true
# 设置访客分流样式
在之前版本中,当企业在管理后台配置了多入口访客分流时,访客请求人工客服,返回多入口选择时,会在消息流中生成一条消息,此种方式对于用户的提示可能较弱,因此,在4.0版本中,在 YSFOptions
中增加了一个配置项 categoryDialogStyle
,用于配置多入口分流的样式,其定义如下:
/**
* 选择咨询分类时自动弹出对话框的样式<br>
* 0 表示不弹出<br>
* 1 表示从中间弹出<br>
* 2 表示从底部弹出
*/
public int categoryDialogStyle;
# 聊天窗口顶部区域自定义
在 V4.7.0 版本中,SDK 加入了聊天界面顶部,App 可以通过实现 AdViewProvider 接口来自定义顶部区域的样式,下面看一下示例代码:
YSFOptions options = new YSFOptions();
IMPageViewConfig imPageViewConfig = new IMPageViewConfig();
imPageViewConfig.adViewProvider = new AdViewProvider() {
@Override
public View getAdview(Context context) {
//每次进入客服界面都会调用该方法
final View view = LayoutInflater.from(context).inflate(R.layout.view_ad_parent,null);
view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
((TextView)view.findViewById(R.id.tv_ad_parent_close)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
view.setVisibility(View.GONE);
}
});
return view;
}
};
options.imPageViewConfig = imPageViewConfig;
从上面的代码可以看到,如果想使用该功能,首先我们需要定义一个 IMPageViewConfig ,然后实现一个 AdViewProvider 并赋值给 IMPageViewConfig。最后把 IMPageViewConfig 赋值给 YSFOption 就可以了。当 SDK 在进入客服界面的时候会调用 getAdView 这个方法,App 方可以根据不同的场景返回不同的 view ,达到展示不同广告的效果。
# 历史消息收起
在 V4.7.0 版本中,SDK 增加了进入客服界面收起历史消息的功能,App 方可以通过如下代码设置是否加载历史消息:
//设置为 false 为默认不加载历史消息,sdk 默认设置是 true 的
YSFOptions options = new YSFOptions();
options.isDefaultLoadMsg = false;
从上面的代码中可以看出来,设置起来还是比较方便的,只需要把 isDefaultLoadMsg 的值改变一下就可以了。这里有个地方需要说明一下,就是收起历史消息的场景,只有在新一通会话才会收起,同一通会话是不会收起的。
# 图片加载
图片加载基本上每个 App 都会用到,各个 App 也都会有自己的实现或者依赖的第三方库。为了避免 SDK 引入第三方图片库后,与 App 自身依赖的图片库冲突,或者与 App 用了不同的库造成浪费,从 2.0.0 版本开始,七鱼 SDK 需要由 App 实现一个图片加载接口,并传给 YSFOptions。 下面是两个常用的第三方图片加载库的接口实现示例,开发者可以直接使用。
图片加载参数中的 uri 格式,只要是 App 选用的图片框架支持即可。但请注意,必须支持本地文件 uri (file://)和 网络文件 uri (http:// 或者 https://) 这两种格式。
# UniversalImageLoader实现
该实现依赖 universal-image-loader 库,需要在工程的 build.gradle 文件中添加依赖:
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
如果使用的 IDE 是 Eclipse,则需要在 libs 中添加 universal-image-loader 的 jar 包。
public class UILImageLoader implements UnicornImageLoader {
private static final String TAG = "UILImageLoader";
@Override
public Bitmap loadImageSync(String uri, int width, int height) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(false)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
// check cache
boolean cached = true;
ImageDownloader.Scheme scheme = ImageDownloader.Scheme.ofUri(uri);
if (scheme == ImageDownloader.Scheme.HTTP
|| scheme == ImageDownloader.Scheme.HTTPS
|| scheme == ImageDownloader.Scheme.UNKNOWN) {
// non local resource
cached = MemoryCacheUtils.findCachedBitmapsForImageUri(uri, ImageLoader.getInstance().getMemoryCache()).size() > 0
|| DiskCacheUtils.findInCache(uri, ImageLoader.getInstance().getDiskCache()) != null;
}
if (cached) {
ImageSize imageSize = (width > 0 && height > 0) ? new ImageSize(width, height) : null;
Bitmap bitmap = ImageLoader.getInstance().loadImageSync(uri, imageSize, options);
if (bitmap == null) {
Log.e(TAG, "load cached image failed, uri =" + uri);
}
return bitmap;
}
return null;
}
@Override
public void loadImage(String uri, int width, int height, final ImageLoaderListener listener) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(false)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
ImageSize imageSize = (width > 0 && height > 0) ? new ImageSize(width, height) : null;
ImageLoader.getInstance().loadImage(uri, imageSize, options, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
if (listener != null) {
listener.onLoadComplete(loadedImage);
}
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
if (listener != null) {
listener.onLoadFailed(failReason.getCause());
}
}
});
}
}
# fresco实现
fresco 库本身提供了一整套图片缓存和加载的功能,由于 SDK 中部分 ImageView 需要做自定义的绘制,因此只会用到其下载,解码和缓存的逻辑。
该实现依赖 fresco 库,需要在工程的 build.gradle 文件中添加依赖:
compile 'com.facebook.fresco:fresco:0.9.0'
如果使用的 IDE 是 Eclipse,则需要在 libs 中添加 fresco 的 jar 包。
public class FrescoImageLoader implements UnicornImageLoader {
private Context context;
public FrescoImageLoader(Context context) {
this.context = context.getApplicationContext();
}
@Override
public Bitmap loadImageSync(String uri, int width, int height) {
Bitmap resultBitmap = null;
ImagePipeline imagePipeline = Fresco.getImagePipeline();
boolean inMemoryCache = imagePipeline.isInBitmapMemoryCache(Uri.parse(uri));
if (inMemoryCache) {
ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(uri));
if (width > 0 && height > 0) {
builder.setResizeOptions(new ResizeOptions(width, height));
}
ImageRequest imageRequest = builder.build();
DataSource<CloseableReference<CloseableImage>> dataSource =
imagePipeline.fetchImageFromBitmapCache(imageRequest, context);
CloseableReference<CloseableImage> imageReference = dataSource.getResult();
try {
if (imageReference != null) {
CloseableImage closeableImage = imageReference.get();
if (closeableImage != null && closeableImage instanceof CloseableBitmap) {
Bitmap underlyingBitmap = ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
if (underlyingBitmap != null && !underlyingBitmap.isRecycled()) {
resultBitmap = underlyingBitmap.copy(Bitmap.Config.RGB_565, false);
}
}
}
} finally {
dataSource.close();
CloseableReference.closeSafely(imageReference);
}
}
return resultBitmap;
}
@Override
public void loadImage(String uri, int width, int height, final ImageLoaderListener listener) {
ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(uri));
if (width > 0 && height > 0) {
builder.setResizeOptions(new ResizeOptions(width, height));
}
ImageRequest imageRequest = builder.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
BaseBitmapDataSubscriber subscriber = new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
if (listener != null) {
new AsyncTask<Bitmap, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Bitmap... params) {
Bitmap bitmap = params[0];
Bitmap result = null;
if (bitmap != null && !bitmap.isRecycled()) {
result = bitmap.copy(Bitmap.Config.RGB_565, false);
}
return result;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
listener.onLoadComplete(bitmap);
} else {
listener.onLoadFailed(null);
}
}
}.execute(bitmap);
}
}
@Override
public void onFailureImpl(DataSource dataSource) {
if (listener != null) {
listener.onLoadFailed(dataSource.getFailureCause());
}
}
};
dataSource.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
}
}
# Glide实现
该实现依赖 Glide 库,需要在工程的 build.gradle 文件中添加依赖:
compile 'com.github.bumptech.glide:glide:3.7.0'
如果使用的 IDE 是 Eclipse,则需要在 libs 中添加 Glide 的 jar 包。
public class GlideImageLoader implements UnicornImageLoader {
private Context context;
public GlideImageLoader(Context context) {
this.context = context.getApplicationContext();
}
@Nullable
@Override
public Bitmap loadImageSync(String uri, int width, int height) {
return null;
}
@Override
public void loadImage(String uri, int width, int height, final ImageLoaderListener listener) {
if (width <= 0 || height <= 0) {
width = height = Integer.MIN_VALUE;
}
Glide.with(context).load(uri).asBitmap().into(new SimpleTarget<Bitmap>(width, height) {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
if (listener != null) {
listener.onLoadComplete(resource);
}
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
if (listener != null) {
listener.onLoadFailed(e);
}
}
});
}
}
# Glide V4 实现
该实现需要依赖 Glide 4.0 以上的版本 具体实现方式如下面代码:
public class GlideImageLoader implements UnicornImageLoader {
private Context context;
public GlideImageLoader(Context context) {
this.context = context.getApplicationContext();
}
@Override
public Bitmap loadImageSync(String uri, int width, int height) {
return null;
}
@Override
public void loadImage(String uri, int width, int height, final ImageLoaderListener listener) {
if (width <= 0 || height <= 0) {
width = height = Integer.MIN_VALUE;
}
Glide.with(context).asBitmap().load(uri).into(new CustomTarget<Bitmap>(width, height) {
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
}
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
if (listener != null) {
listener.onLoadComplete(resource);
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
}
# Picasso实现
该实现依赖 Picasso 库,需要在工程的 build.gradle 文件中添加依赖:
compile 'com.squareup.picasso:picasso:2.5.2'
如果使用的 IDE 是 Eclipse,则需要在 libs 中添加 Picasso 的 jar 包。
public class PicassoImageLoader implements UnicornImageLoader {
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private Context context;
private ExecutorService threadPool;
private Handler uiHandler;
public PicassoImageLoader(Context context) {
this.context = context.getApplicationContext();
uiHandler = new Handler(Looper.getMainLooper());
threadPool = Executors.newFixedThreadPool(CPU_COUNT + 1);
}
@Nullable
@Override
public Bitmap loadImageSync(String uri, int width, int height) {
return null;
}
@Override
public void loadImage(final String uri, final int width, final int height, final ImageLoaderListener listener) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RequestCreator requestCreator = Picasso
.with(context)
.load(uri)
.config(Bitmap.Config.RGB_565);
if (width > 0 && height > 0) {
requestCreator = requestCreator
.resize(width, height)
.centerCrop();
}
Bitmap bitmap = null;
try {
bitmap = requestCreator.get();
} catch (IOException e) {
e.printStackTrace();
}
if (listener == null) {
return;
}
if (bitmap != null && !bitmap.isRecycled()) {
final Bitmap finalBitmap = bitmap;
uiHandler.post(new Runnable() {
@Override
public void run() {
listener.onLoadComplete(finalBitmap);
}
});
} else {
uiHandler.post(new Runnable() {
@Override
public void run() {
listener.onLoadFailed(null);
}
});
}
}
});
}
}
# gif 图片加载
在 V4.9.0 版本中我们开放了加载 gif 图片的功能,用户可以通过实现 UnicornGifImageLoader 接口来完成 gif 图片的加载,是在加载 gif 图片需要两步,第一,实现自己的 UnicornGifImageLoader ,第二,将实现的 UnicornGifImageLoader 设置到 option 中。下面看代码:
//第一步
public class GlideGifImagerLoader implements UnicornGifImageLoader {
Context context;
public GlideGifImagerLoader(Context context) {
this.context = context.getApplicationContext();
}
//当需要加载 gif 图片的时候会回调该方法
@Override
public void loadGifImage(String url, ImageView imageView,String imgName) {
if (url == null || imgName == null) {
return;
}
Glide.with(context).load(url).placeholder(R.drawable.ic_launcher).error(R.drawable.nim_default_img_failed).into(imageView);
}
}
//第二步在设置 App 的 option 的时候需要加上下面一行代码
options.gifImageLoader = new GlideGifImagerLoader(YSFDemoApplication.this);
上面的代码只是展示了 Glide 的时候,因为我们的 loadGifImage 方法已经把 url 和 ImageView 都给到 App 侧了,所以具体实现可以自由发挥
# 手动启动 SDK 的视频和图片界面(只适用 Fragment 的接入方式)
在 V5.4.0 版本中,SDK 增加了启动拍摄视频、拍摄照片、选择本地视频、选择本地照片的四个方法。用户可以通过相关方法启动 SDK 中的界面,然后通过相关回调发送图片和视频,具体操作分为一下几步:
- 创建 UnicornVideoPickHelper 和 UnicornPickImageHelper 并在初始化方法中加入回调
- 通过 UnicornVideoPickHelper 和 UnicornPickImageHelper 启动 SDK 原生视频或相册界面
- 在 Activity 中通过 onActivityResult 方法把视频和图片回传给 UnicornVideoPickHelper 和 UnicornPickImageHelper。
下面看具体实现的代码:
//第一步
//当使用视频相关功能时,创建 UnicornVideoPickHelper
unicornVideoPickHelper = new UnicornVideoPickHelper(this, new UnicornVideoPickHelper.UincornVideoSelectListener() {
@Override
public void onVideoPicked(File file, String md5) {
//当视频处理成功的时候,我们需要去发送视频类型的消息
MediaPlayer mediaPlayer = getVideoMediaPlayer(file);
long duration = mediaPlayer == null ? 0 : mediaPlayer.getDuration();
int height = mediaPlayer == null ? 0 : mediaPlayer.getVideoHeight();
int width = mediaPlayer == null ? 0 : mediaPlayer.getVideoWidth();
IMMessage message = UnicornMessageBuilder.buildVideoMessage(file, duration, width, height, md5);
MessageService.sendMessage(message);
}
});
//当使用图片相关功能时,创建 UnicornVideoPickHelper
unicornPickImageHelper = new UnicornPickImageHelper(this, new UnicornPickImageHelper.SendImageCallback() {
@Override
public void sendImage(File file, String origName, boolean isOrig) {
//当图片处理成功的时候,发送图片类型的消息
IMMessage message = UnicornMessageBuilder.buildImageMessage(UnicornMessageBuilder.getSessionId(), file, file.getName());
MessageService.sendMessage(message);
}
});
//第二步,在 APP 想要启动 SDK 界面的时候,调用相关方法启动
private void addPhoneMenu() {
addImageMenu(R.drawable.ic_action_album).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动选择相册图片
unicornPickImageHelper.goUnicornAlbum(ALBUM_IMAGE_REQUEST_CODE);
}
});
addImageMenu(R.drawable.ic_action_camera).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动拍摄照片
unicornPickImageHelper.goUnicornCapturePhoto(CAPTURE_IMAGE_REQUEST_CODE);
}
});
addImageMenu(R.drawable.ic_action_take_video).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动拍摄视频界面
unicornVideoPickHelper.goCaptureVideo(CAPTURE_VIDEO_REQUEST_CODE);
}
});
addImageMenu(R.drawable.ic_action_select_video).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动选择视频界面
unicornVideoPickHelper.goVideoAlbum(LOCAL_VIDEO_REQUEST_CODE);
}
});
}
//第三步,在 onActivityResult 中把数据回传给 SDK
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CAPTURE_VIDEO_REQUEST_CODE:
//拍摄视频使用
unicornVideoPickHelper.onCaptureVideoResult(data);
break;
case LOCAL_VIDEO_REQUEST_CODE:
//从相册中选择视频使用
unicornVideoPickHelper.onSelectLocalVideoResult(data);
break;
case ALBUM_IMAGE_REQUEST_CODE:
//从相册中选择照片使用
unicornPickImageHelper.onAlbumResult(data);
break;
case CAPTURE_IMAGE_REQUEST_CODE:
//拍照中使用
unicornPickImageHelper.onCapturePhotoResult(data, CAPTURE_IMAGE_PROCESS_REQUEST_CODE);
break;
case CAPTURE_IMAGE_PROCESS_REQUEST_CODE:
//拍照中使用
unicornPickImageHelper.onCapturePhotoPorcessResult(data, CAPTURE_IMAGE_REQUEST_CODE);
break;
}
}
上面的代码已经比较清晰了,如果想查看全部代码,请参考 demo 工程中的 ServiceActivity.class
# 手动启动 SDK 选择文件界面(只适用 Fragment 的接入方式)
在 V5.6.0 版本中, 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;
}
}
上面两步就可以完成文件选择的功能了
# 漫游消息拉取
七鱼客服系统支持账号信息打通,对于企业通过setUserInfo:
接口传入的userId
,服务端会合并不同访客端产生的消息记录。SDK 默认不从服务端拉取漫游消息,仅读取本地数据库持久化数据。若开启漫游消息拉取功能,则在企业调用setUserInfo:
时会联网请求该userId
账户对应的漫游历史消息,并与本地数据库进行对比过滤重复数据。漫游功能独立于聊天页面,可在初始化 SDK 传入的 YsfOption
中配置,可以通过如下代码配置:
YSFOptions options = new YSFOptions();
options.isPullMessageFromServer = true;
在 V5.15.0 版本中,SDK 增加了 pullMessageCount
配置拉取未读消息的数量,如果没有配置该字段默认拉取 20 条,最多拉取 100 条