自渲染广告
自渲染广告
此章节将演示如何请求在原生
环境下请求与展示自渲染广告
自渲染是对原有类型的优化和升级,使用自渲染的 API,您可以为您的应用打造定制式体验
请求自渲染广告
提示
2.4.18 版本起对自渲染广告接入方式进行了调整,旧的方法已移除,需要重新接入
视频广告的MediaView
内部使用了TextureView
,所以需要在开启硬件加速的窗口中才能使用。当 Android API>=14时,硬件加速默认是开启的
调用ZJNativeAd#loadAd
方法请求广告
public class ZJNativeAd {
/**
* 加载广告
*
* @param posId 广告位ID
* @param num 期望请求的数量
* @param loadListener 加载回调
*/
public static void loadAd(@NonNull String posId, int num, @NonNull ZJNativeAdLoadListener loadListener);
/**
* 加载广告
*
* @param activity 当前Activity
* @param posId 广告位ID
* @param num 期望的广告数量(不大于5)
* @param widthPX 展示区域的宽(单位PX)
* @param heightPX 展示区域的高(单位PX)
* @param loadListener 加载回调
*/
public static void loadAd(Activity activity, @NonNull String posId, int num, int widthPX, int heightPX, @NonNull ZJNativeAdLoadListener loadListener);
}
加载回调说明
方法 | 说明 |
---|---|
onError(int code, String msg) | 广告加载出错 code: 错误码 msg: 错误信息 |
onAdLoaded(List<ZJNativeAd> adList) | 广告加载成功 adList: 广告列表 |
展示自渲染广告
在加载成功回调中获取到ZJNativeAd
对象后,需要通过以下方法获取信息、配置回调、绑定视图以展示广告
获取信息
调用以下方法获取自渲染广告的详细信息并展示在自定义容器中
方法 | 说明 |
---|---|
setInteractionListener(ZJNativeAdInteractionListener interactionListener) | 配置交互回调 |
setDownloadListener(ZJNativeAdDownloadListener downloadListener) | 配置下载回调 |
getDislikeEvents() | 获取不喜欢的事件列表 |
dislike(ZJNativeAd.DislikeEvent event) | 回传不喜欢事件 |
boolean addShakeView(@NonNull ViewGroup shakeViewContainer, int widgetSizeDP, int textSizeSP, @Nullable ShakeViewListener shakeViewListener) | 添加摇一摇组件 shakeViewContainer:容器 widgetSizeDP:摇一摇控件的宽高,单位DP textSizeSP 摇一摇文案字体大小,单位SP shakeViewListener 组件回调 return 是否支持手动添加摇一摇组件 |
addSlideView(@NonNull ViewGroup shakeViewContainer, int widgetSizeDP, int textSizeSP, @Nullable ShakeViewListener shakeViewListener) | 添加摇一摇组件 slideViewContainer:容器 widgetSizeDP:摇一摇控件的宽高,单位DP textSizeSP 摇一摇文案字体大小,单位SP repeat:动画重复次数 shakeViewListener 组件回调 |
registerViewForInteraction(Activity activity, ZJNativeAdContainer container, List<View> clickableViews, List<View> creativeViews) | 绑定广告对象和视图 activity: 当前的 activity container: 当前的广告容器,所有的自渲染组件需要在此容器内,且不可以设置点击事件,否则无法正常曝光和计费 clickableViews: 可点击的 view 列表,默认展示下载整改弹框 creativeViews: 带有下载引导文案的View,默认不会触发下载整改弹框 |
registerViewForInteraction(Activity activity, ZJNativeAdContainer container, List<View> clickableViews, List<View> creativeViews, FrameLayout.LayoutParams adLogoParams) | 绑定广告对象和视图 activity: 当前的 activity container: 当前的广告容器,所有的自渲染组件需要在此容器内,且不可以设置点击事件,否则无法正常曝光和计费 clickableViews: 可点击的 view 列表,默认展示下载整改弹框 creativeViews: 带有下载引导文案的View,默认不会触发下载整改弹框 adLogoParms: 广告标志布局参数,(0,0)时不显示角标,部分广告有效 |
bindVideoView(ViewGroup videoContainer, ZJNativeAdVideoListener videoListener) | 绑定广告对象与视频播放器 videoContainer: 播放器的容器 videoListener: 视频播放回调 |
bindVideoView(ViewGroup videoContainer, ZJNativeAdVideoListener videoListener, boolean videoSoundEnable) | 绑定广告对象与视频播放器 videoContainer: 播放器的容器 videoListener: 视频播放回调 videoSoundEnable: 是否开启视频声音 |
onDestroy() | 在 activity#destroy 时调用,以便释放内存 |
getMaterialType() | 获取素材类型, 返回的 type 为视频时,需要通过 bindVideoView 方法绑定播放器,播放视频 |
getActionButtonText() | 按钮提示文字,可能为空字符串 |
getTitle() | 标题,下载类为App应用名,非下载类为产品名称,可能为空字符串 |
getDesc() | 描述,可能为空字符串 |
getIconUrl() | 下载类型的Icon,⾮下载可能返回为空字符串 |
getImageUrl() | 主图Url,可能为空字符串 |
getImageList() | 类型为ZJNativeAd.MaterialType.GROUP_IMG 时需要调用,返回一组图片 URL |
isAppAd() | 是否为下载类广告 |
getDownloadCount() | 获取应用下载次数文案,默认为'0次下载' |
getAppScore() | 应用评分,0-5.0,默认为0 |
getVideoDuration() | 视频时长,默认为0,仅类型为ZJNativeAd.MaterialType.VIDEO 时有效 |
getImageWidth() | 大图素材的宽度,默认为0,仅类型为ZJNativeAd.MaterialType.SINGLE_IMG 时有效 |
getImageHeight() | 大图素材的高度,默认为0,仅类型为ZJNativeAd.MaterialType.SINGLE_IMG 时有效 |
getComplianceInfo() | 获取下载类广告的合规信息,包含应用名称、开发者、版本、apk包大小、隐私政策、权限信息。返回值类型为com.zj.zjsdk.api.v2.nativead.ZJNativeAd.ComplianceInfo 。非下载类广告会返回空对象 |
getSourceInfo() | 获取广告来源信息。返回值类型为com.zj.zjsdk.api.v2.nativead.ZJNativeAd.SourceInfo ,内容可能为空,需要开发者判断并处理 |
MaterialType 说明
ZJNativeAd#getMaterialType()
方法返回的素材类型
类型 | 值 | 说明 |
---|---|---|
ZJNativeAd.MaterialType.UNKNOWN | 0 | 未知类型 |
ZJNativeAd.MaterialType.VIDEO | 1 | 视频类型,需要调用bindVideoView 方法绑定播放器容器与配置视频回调 |
ZJNativeAd.MaterialType.SINGLE_IMG | 2 | 单图类型,包含主图与图标 |
ZJNativeAd.MaterialType.GROUP_IMAGE | 3 | 三小图类型,需要调用getImageList() 方法获取组图信息 |
添加摇一摇与滑一滑互动
注意
需要注意容器和传入的尺寸单位为DP,且不可以小于80DP
添加摇一摇组件
开发者可以根据需求,调用addShakeView
方法传入容器等参数为广告添加摇一摇组件
若返回值为true
,则表示广告支持添加摇一摇组件
若返回值为false
,则表示广告不支持添加摇一摇组件,可以考虑添加滑一滑组件
添加滑一滑组件
开发者可以根据需求,调用addSlideView
方法传入容器等参数为广告添加滑一滑组件
添加滑一滑组件方法与添加摇一摇组件不同,没有返回值,无法直接获取是否添加成功的状态
组件回调
当组件消失时,会回调ZJNativeAd.ShakeViewListener#onDismiss()
方法
绑定视图
视频素材额外绑定
如果素材为视频类型(getMaterialType()
方法返回ZJNativeAd.MaterialType.VIDEO
),需要调用bindVideoView
方法绑定播放器容器与配置视频回调
所有素材都需要绑定视图
在自定义容器内渲染广告信息后,需要调用registerViewForInteraction
方法传入容器
绑定视图,否则无法正常曝光和计费
注意
请确保所有的自渲染组件在此容器内。同时此容器不可以设置点击事件,否则无法正常曝光和计费
交互回调说明
开发者需要调用setInteractionListener(ZJNativeAdInteractionListener)
方法配置交互回调来监听广告的状态
方法 | 说明 |
---|---|
onNativeAdShow() | ⼴告展示回调 每个⼴告仅回调⼀次 |
onNativeAdClick() | ⼴告点击的回调,点击后的动作由 sdk 控制 |
onNativeAdRenderFailed(int code, String msg) | 渲染失败 code: 错误码 msg: 错误信息 |
onDownloadTipsDialogShow() | 广告展示下载合规弹窗 |
onDownloadTipsDialogDismiss() | 广告关闭下载合规弹窗 |
下载回调说明
开发者可以调用setDownloadListener(ZJNativeAdDownloadListener)
方法配置下载回调,并在界面上显示下载进度等信息
方法 | 说明 |
---|---|
onIdle() | 下载挂起 |
onDownloadStarted() | 下载开始 |
onProgressUpdate(int progress) | 下载进度更新 progress: 当前进度(0-100) |
onDownloadPaused(int progress) | 下载暂停 progress: 当前进度(0-100) |
onDownloadFinished() | 下载完成 |
onDownloadFailed() | 下载失败 |
onInstalled() | 安装成功 |
视频回调说明
视频类素材在调用bindVideoView
方法时,需要传入ZJNativeAdVideoListener
对象,并在回调中处理视频播放事件
方法 | 说明 |
---|---|
onVideoReady() | 视频加载完成 |
onVideoStart() | 视频开始播放 |
onVideoPause() | 视频暂停播放 |
onVideoResume() | 视频恢复播放 |
onVideoComplete() | 视频播放完成 |
onVideoError(int code, String msg, String extra) | 视频播放出错 code: 错误码 msg: 错误信息 extra: 额外信息,可能为空 |
不喜欢事件回传
在用户点击关闭按钮时,可以展示调用getDislikeEvents()
方法获取dislike
列表,并在用户选择后调用dislike(ZJNativeAd.DislikeEvent event)
方法回传给广告
Dislike 包括以下预定义事件,若用户手动输入了意见,可以调用构造方法Dislike(String name)
回传DislikeEvent
对象
type | name |
---|---|
0 | 不感兴趣 |
1 | 内容质量差 |
2 | 推荐重复 |
3 | 低俗色情 |
4 | 违法违规 |
5 | 虚假欺诈 |
6 | 诱导点击 |
7 | 疑似抄袭 |
8 | 其他 |
下载六要素说明
下载类广告可以调用getComplianceInfo()
方法获取合规信息,包含应用名称、开发者、版本、apk包大小、隐私政策、权限信息
提示
非下载类广告会返回空对象
部分信息会返回空字符串,需要做判断和处理
应用权限URL返回为空字符串时,需要调用getAppPermissions(PermissionsCallback callback)
方法异步获取权限信息,并根据结果渲染
方法 | 说明 |
---|---|
getAppName() | 应用的名称 |
getAppVersion() | 应用版本信息 |
getAppPackageName() | 应用包名 |
getAppDeveloperName() | 应用开发者名称 |
getAppPrivacyUrl() | 应用隐私政策url,需要使用WebView显示该url |
getAppFunctionDescUrl() | 应用功能介绍url,需要使用WebView展示该url |
getAppPermissionsUrl() | 获取应用权限URL,需要使用WebView展示该url 返回空字符串时,需要调用 getAppPermissions(PermissionsCallback callback) 方法异步获取权限信息 |
getPackageSizeBytes() | 应用apk包的大小,单位byte |
getIcpNumber() | 应用备案号 |
getAppPermissions(PermissionsCallback callback) | 异步获取应用权限 |
异步获取应用权限回调
ZJNativeAd.ComplianceInfo.PermissionsCallback
包含以下回调方法
方法 | 说明 |
---|---|
callback(List<Permission> permissions) | 返回权限信息,可能为空列表 |
应用权限对象说明
ZJNativeAd.ComplianceInfo.Permission
类包含以下获取信息的方法
方法 | 说明 |
---|---|
getName() | 权限原始名称,如android.permission.ACCESS_NETWORK_STATE 可能为空字符串 |
getDesc() | 权限描述,如允许该应用查看网络连接的相关信息,例如存在和连接的网络 可能为空字符串 |
getTitle() | 权限标题,如查看网络连接 可能为空字符串 |
getLevel() | 权限级别 |
提示
返回中的name
、desc
、title
三个值可能部分为空字符串,不会全部为空字符串,需要在渲染时做判断和处理
来源信息说明
部分广告会返回来源信息,需要调用getSourceInfo()
方法获取。方法的返回值不为空,但返回对象的各个字段都有可能为空,需要开发者自行处理
ZJNativeAd.SourceInfo 说明
方法 | 说明 |
---|---|
String getSourceText() | 获取广告来源,如"广告"、"ADX"、"腾讯",可能为空字符串 |
String getSourceImageUrl() | 获取广告来源图片的地址,可能为空字符串 |
String getLogoUrl() | 获取广告来源的LOGO地址,可能为空字符串 |
Bitmap getLogoBitmap() | 广告来源的Bitmap,可能为空 |
渲染说明
当开发者需要在自渲染广告的模板中展示详细的来源信息时,可以通过以上方法来获取广告的来源信息,但需要处理返回为空时的情况
可以参考演示工程的NativeAdSourceView.class
示例代码
public void initView(ZJNativeAd.SourceInfo sourceInfo) {
if (!TextUtils.isEmpty(sourceInfo.getLogoUrl())) {
addImage(sourceInfo.getLogoUrl());
} else if (sourceInfo.getLogoBitmap() != null) {
addImage(sourceInfo.getLogoBitmap());
} else {
addImage(R.drawable.ad_source_logo_def);
}
if (!TextUtils.isEmpty(sourceInfo.getSourceText())) {
addText(sourceInfo.getSourceText());
} else if (!TextUtils.isEmpty(sourceInfo.getSourceImageUrl())) {
addImage(sourceInfo.getSourceImageUrl());
} else {
addText("广告");
}
}
fun init(sourceInfo: SourceInfo) {
when {
sourceInfo.logoUrl.isNotEmpty() -> addImage(sourceInfo.logoUrl)
sourceInfo.logoBitmap != null -> addImage(sourceInfo.logoBitmap!!)
else -> addImage(R.mipmap.ad_source_logo_def)
}
when {
sourceInfo.sourceText.isNotEmpty() -> addText(sourceInfo.sourceText)
sourceInfo.sourceImageUrl.isNotEmpty() -> addImage(sourceInfo.sourceImageUrl)
else -> addText("广告")
}
}
自渲染广告接入示例
ZJNativeAd.loadAd(广告位ID, 1, new ZJNativeAdLoadListener() {
@Override
public void onError(int code, @NonNull String msg) {
Log.e("NativeAd", "onError..." + code + " | " + msg);
}
@Override
public void onAdLoaded(@NonNull List<ZJNativeAd> adList) {
Log.e("NativeAd", "onAdLoaded");
if (!adList.isEmpty()) {
ad = adList.get(0);
getAdInfo(ad);
// 渲染参照Demo工程的NativeAdRender.java
render(ad);
} else {
Log.e("NativeAd", "onAdLoaded...ret is null");
Toast.makeText(NativeDrawActivity.this, "onZjAdLoaded...ret is null", Toast.LENGTH_SHORT).show();
}
}
});
private void getAdInfo(ZJNativeAd ad) {
// 素材类型见ZJNativeAd.MaterialType
Log.e("NativeAd", "素材类型: " + ad.getMaterialType());
// 按钮提示文字,可能为空字符串
Log.e("NativeAd", "按钮提示文字: " + ad.getActionButtonText());
// 标题,可能为空字符串;下载类为App应用名,非下载类为产品名称
Log.e("NativeAd", "标题: " + ad.getTitle());
// 描述,可能为空字符串
Log.e("NativeAd", "描述: " + ad.getDesc());
// 下载类型的AppIcon,⾮下载返回为空字符串
Log.e("NativeAd", "下载类型的AppIcon: " + ad.getIconUrl());
// 主图Url,可能为空字符串
Log.e("NativeAd", "主图Url: " + ad.getImageUrl());
// {@link #getMaterialType()} 返回 {@link MaterialType#GROUP_IMG} 时需要调用;返回一组图片 URL
Log.e("NativeAd", "getImageList: " + ad.getImageList().size());
// 是否为下载类广告
Log.e("NativeAd", "是否为下载类广告: " + ad.isAppAd());
// 获取应用下载次数文案,默认为0; eg: 1000W次下载
Log.e("NativeAd", "应用下载次数文案: " + ad.getDownloadCount());
// 应用评分,0-5.0,默认为0
Log.e("NativeAd", "应用评分: " + ad.getAppScore());
// 视频时长,默认为0
Log.e("NativeAd", "视频时长: " + ad.getVideoDuration());
// 大图素材的宽度,默认为0
Log.e("NativeAd", "大图素材的宽度: " + ad.getImageWidth());
// 大图素材的高度,默认为0
Log.e("NativeAd", "大图素材的高度: " + ad.getImageHeight());
// 获取下载六要素,可能为空
ZJNativeAd.ComplianceInfo complianceInfo = ad.getComplianceInfo();
if (complianceInfo != null) {
// 下载类型因平台不通,可能出现部分字段为空的情况
Log.e("NativeAd", "下载类应用名称: " + complianceInfo.getAppName());
Log.e("NativeAd", "下载类应用版本信息: " + complianceInfo.getAppVersion());
Log.e("NativeAd", "下载类应用包名: " + complianceInfo.getAppPackageName());
Log.e("NativeAd", "下载类应用开发者名称: " + complianceInfo.getAppDeveloperName());
// 应用隐私政策url,需要使用WebView显示该url
Log.e("NativeAd", "下载类应用隐私政策url: " + complianceInfo.getAppPrivacyUrl());
// 应用功能介绍url,需要使用WebView展示该url
Log.e("NativeAd", "下载类应用功能介绍url: " + complianceInfo.getAppFunctionDescUrl());
// 应用apk包的大小,单位byte
Log.e("NativeAd", "下载类应用apk包的大小: " + complianceInfo.getPackageSizeBytes());
Log.e("NativeAd", "下载类应用备案号: " + complianceInfo.getIcpNumber());
// 获取应用权限URL因平台原因,可能为空字符串,需要做判断和处理
String appPermissionsUrl = complianceInfo.getAppPermissionsUrl();
if (!TextUtils.isEmpty(appPermissionsUrl)) {
// 获取应用权限URL,需要使用WebView显示该url
Log.e("NativeAd", "下载类获取应用权限URL: " + appPermissionsUrl);
} else {
// 权限URL为空时,需要使用异步接口获取权限信息并进行自渲染
complianceInfo.getAppPermissions(new ZJNativeAd.ComplianceInfo.PermissionsCallback() {
@Override
public void callback(@Nullable List<ZJNativeAd.ComplianceInfo.Permission> permissions) {
if (permissions == null || permissions.isEmpty()) {
// 部分平台会返回空列表
Log.e("NativeAd", "下载类获取应用权限获取失败");
return;
}
for (ZJNativeAd.ComplianceInfo.Permission permission : permissions) {
// 权限信息不是全部字段都有,title或name一定不为空字符串
// eg:
// {
// "name" : "android.permission.GET_TASKS",
// "desc" : "允许应用程序获取当前的信息或最近运行的任务。",
// "level" : 1,
// "title" : "检索正在运行的应用。"
// }, {
// "name" : "com.asus.msa.SupplementaryDID.ACCESS",
// "desc" : "",
// "level" : 0,
// "title" : "",
// }, {
// "name" : "",
// "desc" : "",
// "level" : 0,
// "title" : "修改或删除您的USB存储设备中的内容",
// }
Log.e("NativeAd", "下载类获取应用权限: " + permission.getName() + " | " + permission.getTitle() + " | " + permission.getDesc() + " | " + permission.getLevel());
}
}
});
}
}
}
ZJNativeAd.loadAd(广告位ID, 1, object : ZJNativeAdLoadListener {
override fun onError(code: Int, msg: String) {
Log.e("NativeAd", "onError...$code | $msg")
}
override fun onAdLoaded(adList: List<ZJNativeAd?>) {
Log.e("NativeAd", "onAdLoaded")
if (!adList.isEmpty()) {
ad = adList[0]
getAdInfo(ad)
// 渲染参照Demo工程的NativeAdRender.java
render(ad)
} else {
Log.e("NativeAd", "onAdLoaded...ret is null")
Toast.makeText(
this@NativeDrawActivity,
"onZjAdLoaded...ret is null",
Toast.LENGTH_SHORT
).show()
}
}
})
private fun getAdInfo(ad: ZJNativeAd) {
// 素材类型见ZJNativeAd.MaterialType
Log.e("NativeAd", "素材类型: " + ad.materialType)
// 按钮提示文字,可能为空字符串
Log.e("NativeAd", "按钮提示文字: " + ad.actionButtonText)
// 标题,可能为空字符串;下载类为App应用名,非下载类为产品名称
Log.e("NativeAd", "标题: " + ad.title)
// 描述,可能为空字符串
Log.e("NativeAd", "描述: " + ad.desc)
// 下载类型的AppIcon,⾮下载返回为空字符串
Log.e("NativeAd", "下载类型的AppIcon: " + ad.iconUrl)
// 主图Url,可能为空字符串
Log.e("NativeAd", "主图Url: " + ad.imageUrl)
// {@link #getMaterialType()} 返回 {@link MaterialType#GROUP_IMG} 时需要调用;返回一组图片 URL
Log.e("NativeAd", "getImageList: " + ad.imageList.size)
// 是否为下载类广告
Log.e("NativeAd", "是否为下载类广告: " + ad.isAppAd)
// 获取应用下载次数文案,默认为0; eg: 1000W次下载
Log.e("NativeAd", "应用下载次数文案: " + ad.downloadCount)
// 应用评分,0-5.0,默认为0
Log.e("NativeAd", "应用评分: " + ad.appScore)
// 视频时长,默认为0
Log.e("NativeAd", "视频时长: " + ad.videoDuration)
// 大图素材的宽度,默认为0
Log.e("NativeAd", "大图素材的宽度: " + ad.imageWidth)
// 大图素材的高度,默认为0
Log.e("NativeAd", "大图素材的高度: " + ad.imageHeight)
// 获取下载六要素,可能为空
val complianceInfo = ad.complianceInfo
if (complianceInfo != null) {
// 下载类型因平台不通,可能出现部分字段为空的情况
Log.e("NativeAd", "下载类应用名称: " + complianceInfo.getAppName())
Log.e("NativeAd", "下载类应用版本信息: " + complianceInfo.getAppVersion())
Log.e("NativeAd", "下载类应用包名: " + complianceInfo.getAppPackageName())
Log.e("NativeAd", "下载类应用开发者名称: " + complianceInfo.getAppDeveloperName())
// 应用隐私政策url,需要使用WebView显示该url
Log.e("NativeAd", "下载类应用隐私政策url: " + complianceInfo.getAppPrivacyUrl())
// 应用功能介绍url,需要使用WebView展示该url
Log.e("NativeAd", "下载类应用功能介绍url: " + complianceInfo.getAppFunctionDescUrl())
// 应用apk包的大小,单位byte
Log.e("NativeAd", "下载类应用apk包的大小: " + complianceInfo.getPackageSizeBytes())
Log.e("NativeAd", "下载类应用备案号: " + complianceInfo.getIcpNumber())
// 获取应用权限URL因平台原因,可能为空字符串,需要做判断和处理
val appPermissionsUrl = complianceInfo.getAppPermissionsUrl()
if (!TextUtils.isEmpty(appPermissionsUrl)) {
// 获取应用权限URL,需要使用WebView显示该url
Log.e("NativeAd", "下载类获取应用权限URL: $appPermissionsUrl")
} else {
// 权限URL为空时,需要使用异步接口获取权限信息并进行自渲染
complianceInfo.getAppPermissions(PermissionsCallback { permissions ->
if (permissions == null || permissions.isEmpty()) {
// 部分平台会返回空列表
Log.e("NativeAd", "下载类获取应用权限获取失败")
return@PermissionsCallback
}
for (permission in permissions) {
// 权限信息不是全部字段都有,title或name一定不为空字符串
// eg:
// {
// "name" : "android.permission.GET_TASKS",
// "desc" : "允许应用程序获取当前的信息或最近运行的任务。",
// "level" : 1,
// "title" : "检索正在运行的应用。"
// }, {
// "name" : "com.asus.msa.SupplementaryDID.ACCESS",
// "desc" : "",
// "level" : 0,
// "title" : "",
// }, {
// "name" : "",
// "desc" : "",
// "level" : 0,
// "title" : "修改或删除您的USB存储设备中的内容",
// }
Log.e(
"NativeAd",
"下载类获取应用权限: " + permission.getName() + " | " + permission.getTitle() + " | " + permission.getDesc() + " | " + permission.getLevel()
)
}
})
}
}
}