Android 原生广告集成
概述
原生广告是一种可以自定义样式的广告形式,能够与应用界面无缝融合,具有以下特点:
- 高度自定义的外观
- 与应用内容自然融合
- 更好的用户体验
- 较高的点击率和转化率
集成步骤
参考demo示例的NativeAdHelper
1. 初始化原生广告
在 Activity 或 Fragment 中初始化原生广告:
kotlin
/**
* 原生广告帮助类
*
* 负责原生广告的加载、展示和生命周期管理
* 支持模板信息流和自渲染信息流两种展示方式
*
* @param activity 上下文Activity
* @param logger 日志打印工具
*/
class NativeAdHelper(private val activity: Activity, private val logger: PrintLogger) {
/**
* 原生广告对象
*
* 用于管理原生广告的加载、展示和销毁
* 通过 UjuAdObject 工厂方法创建,统一管理各类广告对象
*/
private var nativeAd: UjuAdObject? = null
/**
* 加载原生广告
*
* 该方法会创建广告配置,通过工厂方法获取原生广告对象,并设置广告监听器
* 最后调用load()方法开始加载广告
*
* @param pId 广告位ID
*/
fun load(pId: String) {
// 创建广告配置对象,设置广告位ID和广告尺寸
// scenarioKey 为可选参数,用于统计广告展示场景
val adConfig = UjuAdConfig(
placementId = pId, // 原生广告位ID
adViewSize = AdViewSize(width = 600, height = 200) // 广告尺寸:600x200,仅对模板信息流生效
)
// 通过 UjuAdObject 工厂方法获取原生广告对象
nativeAd = UjuAdObject.getNativeObject(activity, adConfig)
// 设置广告监听器,监听广告的各种事件
nativeAd?.setAdObjectListener(object : FeedAdObjectListener {
/**
* 广告加载成功回调
*
* @param placementId 广告位ID
*/
override fun onLoadSuccess(placementId: String) {
// 广告加载成功,此时可以展示,建议展示前判断isReady
logger.add("Native: onLoadSuccess")
}
/**
* 广告加载失败回调
*
* @param error 错误信息
* @param placementId 广告位ID
*/
override fun onLoadError(
error: UjuException,
placementId: String
) {
// 记录加载错误信息
logger.add("Native: onLoadError")
DemoLogUtils.d("Native: onLoadError")
}
/**
* 广告展示过程中出错回调
*
* @param error 错误信息
*/
override fun onAdError(
error: UjuException
) {
// 记录广告错误信息,包含错误消息和错误码
logger.add("Native: onAdError:message:${error.message}, code:${error.code}")
DemoLogUtils.e("Native: onAdError:message:${error.message}, code:${error.code}")
}
/**
* 广告展示成功回调
*/
override fun onAdShow() {
// 通过 getAdInfo() 获取广告信息,直接读取 ecpm 值
val ecpm = nativeAd?.getAdInfo()?.ecpm
logger.add("Native: onAdShow: ecpm:$ecpm")
DemoLogUtils.d("Native: onAdShow: ecpm:$ecpm")
}
/**
* 广告被点击回调
*/
override fun onAdClicked() {
// 记录广告点击事件
logger.add("Native: onAdClicked")
}
/**
* 广告关闭回调
*/
override fun onAdClosed() {
// 记录广告关闭事件
logger.add("Native: onAdClosed")
}
/**
* 广告落地页关闭回调
*/
override fun onLpClosed() {
// 记录落地页关闭事件
logger.add("Native: onLpClosed")
}
})
// 开始加载广告
nativeAd?.load()
// 记录加载广告的事件
logger.add("Native: load, placementId:${adConfig.placementId}")
DemoLogUtils.d("Native: load, placementId:${adConfig.placementId}")
}
/**
* 展示原生广告
*
* 根据广告类型(模板信息流或自渲染信息流)采用不同的展示方式
* 模板信息流:直接调用show()方法展示
* 自渲染信息流:自定义布局,填充数据并绑定交互
*
* @param viewGroup 用于容纳广告的ViewGroup
*/
fun show(viewGroup: ViewGroup) {
// 检查广告对象是否已创建
val adObject = nativeAd ?: run {
logger.add("Native: 请先加载广告")
return
}
// 检查广告是否已准备就绪
if (adObject.isReady()) {
// 判断广告类型:模板信息流或自渲染信息流
if (adObject.getFeedType() == FeedType.EXPRESS) {
// 模板信息流:直接调用show()方法展示
adObject.show(activity, viewGroup)
} else {
// 自渲染信息流:自定义布局,填充数据并绑定交互
// 获取物料数据
val data = adObject.getAdData()
if (data == null) {
// 物料数据为空,提示用户
Toast.makeText(activity, "缺失物料数据", Toast.LENGTH_SHORT).show()
return
}
// 加载自定义广告布局
val adView = activity.layoutInflater.inflate(R.layout.banner_feed_ad_view_layout, null)
// 填充广告数据
adView.findViewById<TextView>(R.id.tvADTitle).text = data.title // 广告标题
adView.findViewById<TextView>(R.id.tvADDesc).text = data.desc // 广告描述
adView.findViewById<TextView>(R.id.tvADSource).text = data.source // 广告来源
adView.findViewById<TextView>(R.id.btnADCreative).text = data.callToAction // 行动号召按钮
// 获取图片视图
val ivAdPic = adView.findViewById<ImageView>(R.id.ivADPic) // 广告大图
val ivAdSmall = adView.findViewById<ImageView>(R.id.ivADSmall) // 广告商图标
// 确定广告图片URL
var imageUrl = ""
if (data.imageUrl != null) {
imageUrl = data.imageUrl.toString() // 使用单张图片URL
} else if (!data.imageUrlList.isNullOrEmpty()) {
imageUrl = data.imageUrlList?.get(0) ?: "" // 使用图片列表中的第一张
}
// 加载广告图片
Glide.with(activity).load(imageUrl).into(ivAdPic)
// 加载广告商图标
Glide.with(activity).load(data.iconUrl).into(ivAdSmall)
// 创建广告视图绑定器,用于绑定广告数据和视图
val binder = NativeAdViewBinder(
titleId = R.id.tvADTitle, // 标题视图ID
descId = R.id.tvADDesc, // 描述视图ID
sourceId = R.id.tvADSource, // 来源视图ID
imageId = R.id.ivADPic, // 图片视图ID
imageViews = listOf(ivAdPic), // 图片视图列表
mediaViewId = R.id.flVideo, // 媒体视图ID(用于视频广告)
iconId = R.id.ivADSmall, // 图标视图ID
callToActionId = R.id.btnADCreative, // 行动号召按钮ID
logoLayoutId = R.id.flADLogo // 广告标识布局ID
)
// 绑定点击响应,使广告可以被点击
adObject.registerViewForInteraction(
activity, // 上下文Activity
adView, // 广告视图
viewGroup, // 容器视图
binder // 视图绑定器
)
}
} else {
// 广告未准备就绪,记录日志
logger.add("Native: 广告还未准备好")
}
}
/**
* 检查广告是否已加载
*
* @return true表示广告对象已创建,false表示未创建
* 注意:此方法仅检查广告对象是否存在,不保证广告已准备就绪
* 要检查广告是否可展示,请使用isReady()方法
*/
fun isLoaded(): Boolean {
return nativeAd != null
}
/**
* 销毁广告对象
*
* 当不再需要广告时,调用此方法销毁广告对象,释放资源
*/
fun destroy() {
// 销毁广告对象
nativeAd?.destroy()
// 置空广告对象引用
nativeAd = null
}
}2. UjuAdConfig 配置说明
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
placementId | String | 是 | 广告位ID |
adViewSize | AdViewSize | 否 | 广告尺寸,指定宽高(单位:dp),仅对模板信息流(FeedType.EXPRESS)生效,默认 AdViewSize()(宽=屏幕宽 dp,高=200) |
scenarioKey | String | 否 | 广告展示场景标识,用于统计广告展示场景 |
userId | String | 否 | 用户 ID,用于个性化策略 |
customData | Map<String, String> | 否 | 自定义数据,透传给广告服务端 |
3. API 变更说明(v2.0)
新版 SDK 采用 UjuAdObject 工厂方法模式,统一管理各类广告对象。
- 广告对象创建:
NativeAdObject(activity, adConfig)已废弃,变更为UjuAdObject.getNativeObject(activity, adConfig) - 变量类型:
NativeAdObject?变更为UjuAdObject? - ecpm 获取:简化链式调用,使用
nativeAd?.getAdInfo()?.ecpm直接获取
4. NativeAdData 物料数据字段
UjuAdObject.getAdData() 返回 NativeAdData 对象,包含以下字段(用于自渲染信息流填充数据):
| 字段 | 类型 | 说明 |
|---|---|---|
title | String | 广告标题 |
desc | String | 广告描述 |
source | String | 广告来源(如广告主名称) |
iconUrl | String | 广告图标 URL |
mediaType | AdMediaType | 媒体类型:SINGLE_IMAGE(1) 单图 / MULTI_IMAGE(2) 多图 / VIDEO(3) 视频 |
imageUrl | String | 单张图片 URL(单图模式使用) |
imageUrlList | List<String> | 图片 URL 列表(多图模式使用) |
callToAction | String | 行动号召文案(如「立即下载」) |
5. NativeAdViewBinder 视图绑定器字段
NativeAdViewBinder 用于自渲染信息流绑定广告视图与数据,完整字段如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
titleId | Int | 是 | 标题视图 ID |
descId | Int | 是 | 描述视图 ID |
sourceId | Int | 是 | 来源视图 ID |
iconId | Int | 是 | 图标视图 ID |
imageId | Int | 否 | 主图片视图 ID |
imageViews | List<ImageView> | 否 | 图片视图列表(多图模式使用) |
mediaViewId | Int | 否 | 媒体视图 ID(用于视频广告) |
callToActionId | Int | 是 | 行动号召按钮视图 ID |
logoLayoutId | Int | 否 | 广告标识布局 ID |
clickViewsIds | List<Int> | 否 | 可点击视图 ID 列表(除默认点击区域外的额外点击视图) |
dislikeId | Int | 否 | 不感兴趣按钮视图 ID |
6. UjuAdInfo 广告信息字段
UjuAdObject.getAdInfo() 返回 UjuAdInfo 对象,包含广告元数据:
| 字段 | 类型 | 说明 |
|---|---|---|
ecpm | Float | 广告出价(eCPM),单位:元 |
placementId | String | 广告位 ID |
soltId | String | 广告代码位 ID(注:源码拼写为 soltId) |
platformId | Int | 广告平台 ID(参见 AdPlatformType 枚举) |
adFormat | Int | 广告格式 |
最佳实践
1. 广告布局设计
- 与应用风格一致:使用与应用相同的设计语言
- 清晰的广告标识:确保用户能够识别这是广告
- 合理的信息层次:标题、描述、品牌信息清晰可见
- 响应式设计:适应不同屏幕尺寸
2. 优化建议
- 预加载广告:在需要展示广告前提前加载
- 缓存机制:实现广告缓存,提高展示速度
- 交替布局:为不同广告创建多种布局
- 性能优化:避免过度渲染和内存泄漏
3. 避免的问题
- 不要隐藏广告标识
- 不要修改广告内容
- 不要干扰广告的正常展示
- 不要在广告中添加无关内容
常见问题
Q: 原生广告为什么不显示?
A: 可能的原因:
- 广告单元 ID 不正确
- 网络连接问题
- 广告库存不足
- 布局问题(容器不可见或尺寸为 0)
- 渲染代码有问题
Q: 如何提高原生广告的效果?
A: 建议:
- 精心设计广告布局
- 确保广告与应用内容相关
- 优化广告加载和渲染
- 测试不同的广告位置
- 分析广告效果数据
Q: 原生广告可以在 RecyclerView 中使用吗?
A: 可以,实现方法:
- 在 ViewHolder 中管理原生广告
- 为广告项创建专门的布局
- 正确处理广告的加载和渲染
- 避免频繁创建和销毁广告
Q: 如何从旧版 API 迁移到新版?
A: 主要变更点:
- 将
NativeAdObject(activity, adConfig)替换为UjuAdObject.getNativeObject(activity, adConfig) - 将变量类型声明从
NativeAdObject?改为UjuAdObject? - 将 ecpm 获取方式简化为
nativeAd?.getAdInfo()?.ecpm - 可选:在
UjuAdConfig中添加scenarioKey参数用于场景统计
