主题
激励视频广告
文档版本 v1.0.0
本页对应 iOS SDK 1.0.0,与离线包 docs/ 目录里的同一份文档内容一致。其他版本见文档中心。
与另两种广告位最大的区别是它带「奖励」语义,而奖励一旦有现金价值就必须经服务端验证。
完整示例
objc
// RewardViewController.h
#import <UIKit/UIKit.h>
@interface RewardViewController : UIViewController
@endobjc
// RewardViewController.m
#import "RewardViewController.h"
#import <AdMixCore/AdMixCore.h>
@interface RewardViewController () <AdMixRewardAdDelegate>
@end
@implementation RewardViewController {
AdMixRewardAd *_rewardAd;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:@"看视频领奖励" forState:UIControlStateNormal];
[button addTarget:self action:@selector(playReward) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(0, 0, 200, 44);
button.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
button.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
| UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
[self.view addSubview:button];
}
/// 用户点按钮时才加载。入口页打开时预加载过的话,这里直接命中缓存
- (void)playReward {
[_rewardAd destroy]; // 重新 load 前先销毁上一个
_rewardAd = [[AdMixRewardAd alloc] initWithUnitId:@"reward_main"];
_rewardAd.delegate = self;
[_rewardAd loadAd];
}
#pragma mark - AdMixRewardAdDelegate
- (void)rewardAdDidLoad:(AdMixRewardAd *)ad adnType:(NSString *)adnType {
// 返回 NO = 素材已过期或已展示过,按无广告降级
if (![ad showFromViewController:self]) [self showNoAd];
}
- (void)rewardAd:(AdMixRewardAd *)ad didFailWithError:(NSError *)error {
// 用户点了按钮在等结果,失败时必须给反馈。错误码对照见《错误码参考》
NSLog(@"激励视频加载失败 %@ %ld %@", error.domain, (long)error.code, error.localizedDescription);
[self showNoAd];
}
- (void)showNoAd {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"暂时没有可观看的视频"
message:@"请稍后再试"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"好" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)rewardAdDidReward:(AdMixRewardAd *)ad {
/* 发放奖励。奖励有现金价值时这里只更新界面,发奖以服务端通知为准 */
}
- (void)rewardAdDidFinishPlaying:(AdMixRewardAd *)ad { }
- (void)rewardAdDidClose:(AdMixRewardAd *)ad { }
- (void)dealloc {
[_rewardAd destroy];
}
@end一次 loadAd 的广告只能展示一次,展示后需重新创建并 loadAd。 加载中再次调用 loadAd 会被静默忽略并打一条警告日志 —— 选择静默忽略而不是回调失败, 是因为回调失败会让业务侧以为「没广告」从而错误降级。
delegate 是弱引用,广告对象本身要由页面强持有,否则加载回调到达之前对象已被释放。
发奖与播放完成是两件事
判据只能是 rewardAdDidReward:,与 rewardAdDidFinishPlaying: 无关,也不要假设先后顺序。
- 真机实测两家的回调顺序都是「展示 → 发奖 → 播放完成 → 关闭」,发奖早于播放完成。 等播放完成再发奖会导致延迟发放,用户中途关闭时还会漏发
- 激励条件不一定是「看完视频」。部分创意的条件是「体验 App 15 秒」或「点击广告」, 不播完视频也会发奖;反过来只播完视频也未必发奖
rewardAdDidFinishPlaying: 只适合做「视频播完」这类界面反馈。
关闭行为
广告平台点「跳过」或关闭按钮时可能先弹挽留弹窗(如「再看一个获取更多奖励 / 坚持退出」), 用户坚持退出后才真正关闭。业务侧不要假设「点跳过 == 广告结束」,一切以 rewardAdDidClose: 为准。
奖励有现金价值时必须接服务端验证
客户端的发奖回调可被 Hook、重放伪造
奖励能提现、抵扣、抽奖,或者有人会为它刷量时,rewardAdDidReward: 只用于界面反馈 (如提示「奖励发放中」),真实发奖以服务端通知为准。
接法见《服务端对接》。奖励只是「多一次复活」这类无价值的,只用客户端回调即可。
服务端验证参数必须在 loadAd 之前设置,对之后每次 loadAd 都生效。 设置后该对象的请求不使用预加载缓存(缓存里的广告在请求时没有这次的用户 ID):
objc
_rewardAd = [[AdMixRewardAd alloc] initWithUnitId:@"reward_main"];
// 用户 ID、自定义数据,均可传 nil。用户 ID 不要放手机号等敏感信息
[_rewardAd setServerSideVerificationUserId:currentUserId customData:@"scene=daily_sign"];
_rewardAd.delegate = self;
[_rewardAd loadAd];预加载
激励视频入口页打开时预加载,用户点按钮时大概率已经就绪:
objc
[AdMixRewardAd preloadUnitId:@"reward_main"];未命中缓存但有预加载在飞时,loadAd 会等它的结果而不重复请求。 带了服务端验证参数的请求既不取缓存也不等预加载。详见《预加载与缓存》。
Swift 写法
swift
import AdMixCore
final class RewardViewController: UIViewController, AdMixRewardAdDelegate {
private var rewardAd: AdMixRewardAd?
func playReward(userId: String) {
rewardAd?.destroy() // 重新 load 前先销毁上一个
let ad = AdMixRewardAd(unitId: "reward_main")
ad.setServerSideVerification(userId: userId, customData: "scene=daily_sign") // 可选
ad.delegate = self
rewardAd = ad
ad.load()
}
func rewardAdDidLoad(_ ad: AdMixRewardAd, adnType: String) {
if !ad.show(from: self) { /* 按无广告降级 */ }
}
func rewardAd(_ ad: AdMixRewardAd, didFailWithError error: Error) {}
// 发奖与播放完成是两件事,不要互相替代
func rewardAdDidReward(_ ad: AdMixRewardAd) { /* 奖励有现金价值时只更新界面,发奖以服务端通知为准 */ }
func rewardAdDidFinishPlaying(_ ad: AdMixRewardAd) {}
func rewardAdDidClose(_ ad: AdMixRewardAd) {}
deinit { rewardAd?.destroy() }
}