news 2026/7/11 23:12:40

Unity广告变现进阶:自定义展示与事件处理实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity广告变现进阶:自定义展示与事件处理实战指南

1. 项目概述:为什么我们需要自定义广告与事件处理?

如果你在Unity里用过Google Mobile Ads插件,大概率是从官方文档或者一个简单的“Hello World”示例开始的:导入插件,拖个预制体,调用RewardedAd.Load,然后祈祷广告能正常显示。这确实能让广告跑起来,但对于一个真正要上线的商业项目来说,这仅仅是万里长征的第一步。我见过太多项目,广告收入上不去,用户留存受影响,排查问题时像无头苍蝇,根源往往就在于对广告SDK的理解还停留在“能用”的层面,而没有深入到“用好”的层次。

“自定义广告展示与事件处理”这个标题,听起来有点技术化,但它的核心目标非常实际:提升广告变现效率,优化用户体验,并建立可靠的数据监控体系。简单加载一个标准横幅或激励视频,SDK会帮你处理默认的展示逻辑,但这就像给你一辆标准配置的汽车,你只能沿着预设的道路开。而自定义,意味着你可以根据自己游戏的“地形”——比如用户的关卡进度、付费意愿、网络环境——来动态调整广告的展示策略、样式,并精准捕获每一次广告交互的“心跳”(事件),把这些数据变成优化收入的燃料。

举个例子,你的游戏在欧美和东南亚地区都上线了。默认的广告展示可能都一样,但通过自定义,你可以为欧美用户展示更符合他们审美、尺寸更大的自适应横幅,而为网络条件可能较差的东南亚用户,预先加载好广告并采用更轻量的展示动画,避免卡顿。同时,每一次广告展示带来的具体收入(哪怕是0.001美元)、是哪个广告网络提供的(如AdMob、Unity Ads、AppLovin)、用户是否看完了视频,这些事件你都能实时捕获并发送给自己的数据分析后台。这样,你就能清晰地知道,是哪个地区的哪种广告格式、来自哪个广告源的收益最高,从而动态调整你的广告瀑布流或竞价策略。

所以,这篇教程要解决的,就是带你跨过“集成”的门槛,进入“优化”和“掌控”的领域。无论你是独立开发者还是团队中的技术负责人,理解并实施这些高级技巧,都将直接影响到你项目的广告营收生命线。

2. 核心思路拆解:从“被动加载”到“主动管理”

在深入代码之前,我们必须先建立一个清晰的顶层设计思路。使用GMA Unity插件的进阶之路,就是从被动的“事件响应者”转变为主动的“广告生命周期管理者”。

2.1 理解广告对象与事件驱动的架构

Google Mobile Ads Unity插件(以下简称GMA插件)的核心是一个典型的事件驱动模型。无论是BannerViewInterstitialAd还是RewardedAd,每个广告对象都提供了一系列事件(event),如OnAdPaidOnAdFullScreenContentOpenedOnAdClicked等。基础用法是订阅这些事件,在回调里写个Debug.Log。而高级用法,则是围绕这些事件构建一套完整的业务逻辑层。

核心转变在于:我们不再仅仅为了显示广告而监听事件,而是为了以下目标:

  1. 数据采集:将OnAdPaid(广告付费)事件中的详细收入数据(价值、货币、精度、广告源信息)实时上报至自己的数据分析平台(如自建后端或Firebase Analytics)。
  2. 流程控制:利用OnAdFullScreenContentClosed(广告关闭)事件,决定接下来是恢复游戏逻辑、发放奖励,还是触发下一个广告的预加载。
  3. 用户体验定制:通过OnAdImpressionRecorded(广告展示记录)等事件,结合自定义的展示逻辑(比如延迟展示、条件触发),来减少对游戏流程的干扰。
  4. 异常处理与调试:通过OnAdFailedToShow(广告展示失败)和加载错误回调,获取详细的错误码和响应信息,实现端到端的错误监控和快速排查。

2.2 设计可扩展的自定义广告管理器

直接在每个需要广告的MonoBehaviour里写加载和事件处理代码是灾难的开始。随着广告类型和场景增多,代码会变得难以维护和复用。因此,我们的第一个实操要点是构建一个中心化的AdManager

这个管理器不应是简单的静态方法集合,而应该是一个能处理以下职责的单例或服务类:

  • 广告单元ID管理:根据平台(Android/iOS)和测试模式,动态返回正确的广告单元ID。
  • 广告生命周期管理:统一处理广告的加载、展示、销毁,并维护广告对象的引用,防止内存泄漏。
  • 事件路由与转发:作为所有广告事件的中枢,将原生的事件回调转换为更易用的自定义事件(如Action<AdRevenueData>),并分发给订阅了这些事件的游戏模块(如经济系统、分析系统)。
  • 策略执行:嵌入自定义展示逻辑,例如“同一关卡30分钟内不重复展示插页广告”、“仅当WiFi环境下预加载视频广告”。

一个常见的架构误区是试图创建一个能处理所有广告类型的“上帝类”。更好的实践是采用组合模式:一个顶层的AdManager负责协调和提供公共接口,其下为每种广告类型(BannerHandlerRewardedAdHandlerInterstitialHandler)设立专门的处理类。这样,当Google SDK API发生变更时,你只需要修改对应的Handler,而不影响全局。

3. 深度解析:自定义广告展示的实战策略

自定义展示不仅仅是“在哪里显示广告”,更是“在什么时机、以什么方式、给谁显示广告”。下面我们拆解几个关键场景。

3.1 横幅广告的自定义:超越预制体

很多开发者直接使用插件自带的BannerView预制体,这限制了自定义能力。更高级的做法是以编程方式创建和管理BannerView

public class CustomBannerHandler { private BannerView _bannerView; private AdPosition _currentPosition = AdPosition.Bottom; private bool _isShowing = false; public void CreateAndLoadBanner(string adUnitId) { // 如果已存在,先销毁 if (_bannerView != null) { _bannerView.Destroy(); } // 1. 创建尺寸适配器:使用自适应横幅以获得最佳收益 AdSize adaptiveSize = AdSize.GetCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(AdSize.FullWidth); // 2. 以编程方式实例化BannerView _bannerView = new BannerView(adUnitId, adaptiveSize, _currentPosition); // 3. 订阅关键事件 _bannerView.OnBannerAdLoaded += OnBannerAdLoaded; _bannerView.OnBannerAdLoadFailed += OnBannerAdLoadFailed; _bannerView.OnAdPaid += OnAdPaid; _bannerView.OnAdImpressionRecorded += OnAdImpressionRecorded; // 4. 加载广告 AdRequest request = new AdRequest(); _bannerView.LoadAd(request); } private void OnBannerAdLoaded() { Debug.Log("横幅广告加载成功"); _isShowing = true; // 这里可以触发游戏UI的适配,例如将游戏内容区域上移 } private void OnBannerAdLoadFailed(LoadAdError error) { Debug.LogError($"横幅广告加载失败: {error.GetMessage()}"); // 可以实现重试逻辑,例如30秒后重试 } // 自定义展示控制方法 public void ShowBanner() { if (_bannerView != null && !_isShowing) { _bannerView.Show(); _isShowing = true; } } public void HideBanner() { if (_bannerView != null && _isShowing) { _bannerView.Hide(); _isShowing = false; } } public void ChangePosition(AdPosition newPosition) { if (_bannerView != null) { _bannerView.SetPosition(newPosition); _currentPosition = newPosition; } } }

为什么这么做?

  • 灵活性:你可以在运行时动态改变横幅的位置、大小,甚至根据游戏横竖屏切换来调整。
  • 性能:避免使用Unity的GameObjectRectTransform来包裹一个原生广告视图可能带来的性能开销和层级问题。
  • 控制力:你可以精确控制横幅的显示(Show)和隐藏(Hide)时机,而不是依赖预制体的Active状态。例如,在播放过场动画或进入核心玩法时隐藏横幅,在菜单界面再显示。

注意BannerView.Hide()只是将广告视图设为不可见,广告对象仍在内存中并可能继续产生曝光。如果长时间不需要(如玩家进入付费去广告模式),应调用Destroy()释放资源。

3.2 插页与激励广告的展示时机策略

对于全屏广告,粗暴地在场景切换时展示会严重破坏用户体验。我们需要策略。

插页广告策略示例:

public class InterstitialStrategy { private InterstitialAd _interstitialAd; private DateTime _lastShowTime; private readonly TimeSpan _minInterval = TimeSpan.FromMinutes(2); // 最小间隔2分钟 private int _gameSessionsSinceLastAd = 0; public bool CanShowInterstitial() { // 策略1:时间间隔控制 if (DateTime.Now - _lastShowTime < _minInterval) { return false; } // 策略2:游戏会话次数控制(例如,每3次游戏会话展示一次) if (_gameSessionsSinceLastAd < 3) { return false; } // 策略3:检查广告是否已加载且就绪 if (_interstitialAd == null || !_interstitialAd.CanShowAd()) { // 触发异步加载,为下一次机会准备 LoadInterstitial(); return false; } // 策略4:避免在关键流程中展示(如付费点前、最终Boss战) if (GameState.IsInCriticalMoment()) { return false; } return true; } public void RecordGameSession() { _gameSessionsSinceLastAd++; } public void ShowInterstitialIfEligible() { if (CanShowInterstitial()) { _interstitialAd.Show(); _lastShowTime = DateTime.Now; _gameSessionsSinceLastAd = 0; // 展示后立即开始加载下一个,实现“预加载”流水线 LoadInterstitial(); } } }

激励视频广告的“软提示”自定义:不要直接弹出一个硬生生的“看广告获得奖励”窗口。可以设计一个更柔和的流程:

  1. 玩家点击“获取双倍奖励”按钮。
  2. 先展示一个精美的游戏内UI,说明奖励内容和需要观看一个短视频。
  3. 用户确认后,再调用RewardedAd.Show()。如果广告未准备好,在此处给用户一个友好的提示(如“视频正在加载,请稍候…”),而不是一个错误弹窗。
  4. OnAdFullScreenContentClosed回调中,必须首先检查RewardedAdCanShowAd状态以及通过GetRewardItem()确认奖励是否应被授予,然后再发放游戏内奖励。这是防止因广告未完整播放而错误发放奖励的关键。

4. 事件处理的精髓:从回调到可观测数据流

事件处理是自定义的核心。我们需要系统性地捕获、加工和利用每一个广告事件。

4.1 标准化广告收入数据上报

OnAdPaid事件是收入的直接体现。官方文档给出了一个示例,但我们需要将其封装成适合自己后端接口的数据结构。

[System.Serializable] public class AdRevenueData { public string ad_unit_id; public string ad_format; // "BANNER", "REWARDED", "INTERSTITIAL" public long value_micros; // 收入微值 public string currency_code; // 货币代码,如"USD" public string precision; // "ESTIMATED", "PUBLISHER_PROVIDED", "PRECISE", "UNKNOWN" public string ad_source_id; public string ad_source_name; public string mediation_group_name; public string mediation_ab_test_name; public string mediation_ab_test_variant; public long timestamp_unix_ms; // 事件发生时间戳 public string platform; // "ANDROID", "IOS" } public class AdEventProcessor { public void ProcessPaidEvent(AdValue adValue, ResponseInfo responseInfo, string adUnitId, string adFormat) { var revenueData = new AdRevenueData(); revenueData.ad_unit_id = adUnitId; revenueData.ad_format = adFormat; revenueData.value_micros = adValue.Value; revenueData.currency_code = adValue.CurrencyCode; revenueData.precision = adValue.Precision.ToString(); revenueData.timestamp_unix_ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); revenueData.platform = Application.platform == RuntimePlatform.Android ? "ANDROID" : "IOS"; // 从ResponseInfo中提取关键中介信息 if (responseInfo != null) { var loadedAdapterResponseInfo = responseInfo.GetLoadedAdapterResponseInfo(); if (loadedAdapterResponseInfo != null) { revenueData.ad_source_id = loadedAdapterResponseInfo.AdSourceId; revenueData.ad_source_name = GetAdSourceName(loadedAdapterResponseInfo); // 使用自定义方法处理“Custom Event” } var extras = responseInfo.GetResponseExtras(); if (extras != null) { extras.TryGetValue("mediation_group_name", out revenueData.mediation_group_name); extras.TryGetValue("mediation_ab_test_name", out revenueData.mediation_ab_test_name); extras.TryGetValue("mediation_ab_test_variant", out revenueData.mediation_ab_test_variant); } } // 立即上报到你的分析服务器 SendToAnalyticsServer(revenueData); // 同时,你也可以选择在本地缓存一份,用于离线补偿或批量上报 CacheRevenueDataLocally(revenueData); } // 处理自定义事件广告源名称(关键!) private string GetAdSourceName(AdapterResponseInfo info) { if (info == null) return "Unknown"; string name = info.AdSourceName; if (name == "Custom Event") { // 根据AdapterClassName来区分不同的自定义事件网络 string className = info.AdapterClassName; #if UNITY_ANDROID if (className.Contains("com.mycompany.mediation.mynetwork")) { name = "MyAdNetwork (Custom)"; } #elif UNITY_IOS if (className.Contains("MyNetworkCustomEvent")) { name = "MyAdNetwork (Custom)"; } #endif } return name; } private async void SendToAnalyticsServer(AdRevenueData data) { string json = JsonUtility.ToJson(data); // 使用UnityWebRequest或你喜欢的HTTP客户端发送POST请求 // 注意:确保此操作不会阻塞主线程,考虑使用异步操作。 } }

关键点解析

  • 立即上报:在OnAdPaid回调中同步或立即异步上报数据。避免先存入队列再批量处理,除非你有强大的离线补偿机制,否则容易因应用崩溃或进程被杀导致数据丢失。
  • 货币与精度value_micros是微单位,1,000,000微单位等于1个货币单位。precision字段告诉你这个数值的可靠程度,对于优化决策很重要(例如,ESTIMATED值可用于趋势分析,而PRECISE值用于精确结算)。
  • 自定义事件广告源:这是很多开发者忽略的坑。如果你接入了第三方中介网络并通过自定义事件集成,默认的AdSourceName只是“Custom Event”。你必须像上面代码那样,通过AdapterClassName来区分具体的广告网络,否则你的收入报表将无法区分流量来源,失去优化意义。

4.2 构建全局事件总线与游戏逻辑解耦

不要让UI脚本或游戏管理器直接监听广告事件。引入一个简单的事件总线(Event Bus)或消息系统来解耦。

public static class AdEventBus { public static event Action<AdRevenueData> OnAdRevenuePaid; public static event Action<string> OnRewardGranted; // 奖励类型 public static event Action OnInterstitialClosed; public static event Action<string> OnAdFailedToShow; // 错误信息 public static void PublishAdRevenue(AdRevenueData data) => OnAdRevenuePaid?.Invoke(data); public static void PublishRewardGranted(string rewardType) => OnRewardGranted?.Invoke(rewardType); public static void PublishInterstitialClosed() => OnInterstitialClosed?.Invoke(); public static void PublishAdFailed(string error) => OnAdFailedToShow?.Invoke(error); } // 在AdManager或事件处理器中触发事件 private void HandleRewardedAdPaid(AdValue adValue, ResponseInfo info) { var data = ProcessPaidEvent(adValue, info, "rewarded_unit_id", "REWARDED"); AdEventBus.PublishAdRevenue(data); } private void HandleRewardedAdUserEarnedReward(Reward reward) { AdEventBus.PublishRewardGranted(reward.Type); } // 在游戏的其他模块(如经济系统、UI控制器)中订阅事件 public class RewardSystem : MonoBehaviour { private void OnEnable() { AdEventBus.OnRewardGranted += HandleReward; } private void OnDisable() { AdEventBus.OnRewardGranted -= HandleReward; } private void HandleReward(string rewardType) { if (rewardType == "double_coins") { PlayerInventory.AddCoins(PlayerInventory.Coins); ShowRewardPopup("金币翻倍!"); } } }

这样做的好处是,广告相关的代码和游戏业务逻辑完全分离。当你需要修改奖励发放逻辑,或者添加新的监听者(比如成就系统)时,无需改动AdManager的代码。

5. 高级集成:中介组与A/B测试的精细化处理

当你的广告集成了多个来源(AdMob, Unity Ads, AppLovin等)并通过Google中介进行优化时,事件数据会包含丰富的上下文信息。

5.1 利用ResponseInfo进行深度诊断

ResponseInfo对象是排查问题和进行深度分析的宝藏。除了用于收入上报,你还可以在调试模式或用户反馈中记录这些信息。

private void LogResponseInfoDetails(ResponseInfo responseInfo) { if (responseInfo == null) { Debug.LogWarning("ResponseInfo is null."); return; } Debug.Log($"Mediation Adapter Class: {responseInfo.MediationAdapterClassName}"); Debug.Log($"Response ID: {responseInfo.GetResponseId()}"); // 获取所有广告源的响应信息 List<AdapterResponseInfo> adapterInfoList = responseInfo.GetAdapterResponses(); foreach (var adapterInfo in adapterInfoList) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"Ad Source: {adapterInfo.AdSourceName}"); sb.AppendLine($" - AdSourceId: {adapterInfo.AdSourceId}"); sb.AppendLine($" - AdSourceInstance: {adapterInfo.AdSourceInstanceName}"); sb.AppendLine($" - Latency: {adapterInfo.LatencyMillis} ms"); sb.AppendLine($" - Ad Unit Mapping: {JsonUtility.ToJson(adapterInfo.AdUnitMapping)}"); // 特别关注加载失败的广告源 if (adapterInfo.AdError != null) { sb.AppendLine($" - ERROR: Code={adapterInfo.AdError.GetCode()}, Msg={adapterInfo.AdError.GetMessage()}"); // 这里可以上报错误到你的监控系统 ReportAdErrorToServer(adapterInfo.AdSourceName, adapterInfo.AdError.GetMessage()); } Debug.Log(sb.ToString()); } }

OnAdFailedToLoadOnAdFailedToShow回调中调用此方法,你可以清晰地看到广告请求经过了哪些中介适配器,每个广告源的加载耗时、是否成功、失败的具体原因是什么。这对于优化你的中介瀑布流优先级至关重要。

5.2 处理中介A/B测试变量

如果你在AdMob后台设置了中介A/B测试(用于测试不同瀑布流配置的效果),测试分组信息会通过ResponseInfo.GetResponseExtras()传递。

private void HandleMediationExperiment(ResponseInfo responseInfo) { var extras = responseInfo.GetResponseExtras(); if (extras != null && extras.ContainsKey("mediation_ab_test_variant")) { string variant = extras["mediation_ab_test_variant"]; // 例如 "group_a", "control" string experimentName = extras["mediation_ab_test_name"]; // 将实验变量与本次广告事件关联上报 AdRevenueData data = ... // 你的收入数据 data.mediation_ab_test_name = experimentName; data.mediation_ab_test_variant = variant; // 同时,你也可以在游戏内根据不同的实验组调整策略(谨慎使用) // PlayerPrefs.SetString("CurrentMediationVariant", variant); } }

通过将实验变量与收入数据一同上报,你可以在自己的数据分析平台(如Google Data Studio, Looker)中,精确地对比不同实验组的eCPM(千次展示收益)和填充率,用数据驱动决策,而不是凭感觉调整。

6. 性能优化与疑难排查实战指南

高级集成的另一面是复杂性的提升,随之而来的是更多的性能陷阱和问题。

6.1 内存管理与对象生命周期

常见坑点:广告对象未正确销毁。

  • 现象:游戏运行一段时间后内存持续增长,或出现不可预知的广告展示行为。
  • 根因BannerViewInterstitialAdRewardedAd对象在不再需要时(如场景切换、玩家购买去广告后)没有调用Destroy()方法。这些对象持有原生代码的引用,会导致内存泄漏。
  • 解决方案:在广告管理器或处理类中实现明确的销毁逻辑。
public class AdManager : MonoBehaviour { private Dictionary<string, object> _activeAds = new Dictionary<string, object>(); public void RequestAndCacheInterstitial(string placement) { InterstitialAd.Load(..., (ad, error) => { if (error == null) { _activeAds[placement] = ad; // 订阅事件... } }); } public void CleanupPlacement(string placement) { if (_activeAds.TryGetValue(placement, out var adObj)) { if (adObj is BannerView banner) banner.Destroy(); else if (adObj is InterstitialAd interstitial) interstitial.Destroy(); else if (adObj is RewardedAd rewarded) rewarded.Destroy(); _activeAds.Remove(placement); } } void OnDestroy() { // 游戏对象销毁时,清理所有广告 foreach (var ad in _activeAds.Values) { /* 类型判断并Destroy */ } _activeAds.Clear(); } }

6.2 主线程事件回调的陷阱

官方警告:为了保持展示机会层级的广告收入跟踪的准确性,应避免使用MobileAds.RaiseAdEventsOnUnityMainThread = true;。因为这会强制所有广告事件在Unity主线程触发,如果主线程繁忙,可能导致事件丢失或延迟。

最佳实践

  • 保持默认设置:让广告事件在它们自己的线程上触发。
  • 如果你必须在主线程处理UI更新:在事件回调中,使用MainThreadDispatcher(许多框架如UniTask、或自己实现的队列)来将关键操作派发到主线程,而不是阻塞事件线程。
// 假设你有一个主线程分发器 public class MainThreadDispatcher : MonoBehaviour { private static Queue<Action> _executionQueue = new Queue<Action>(); public void Update() { lock (_executionQueue) { while (_executionQueue.Count > 0) { _executionQueue.Dequeue().Invoke(); } } } public static void Enqueue(Action action) { lock (_executionQueue) { _executionQueue.Enqueue(action); } } } // 在广告事件回调中 private void HandleAdPaidEvent(AdValue adValue) { // 数据处理(非UI操作)可以直接在这里做,很快 var revenueData = ProcessRevenueData(adValue); SendToAnalyticsAsync(revenueData); // 异步上报,不阻塞 // 如果需要更新UI(比如显示一个“+$0.01”的飘字) MainThreadDispatcher.Enqueue(() => { UIManager.Instance.ShowEarningPopup(revenueData.value_micros / 1000000f); }); }

6.3 典型问题排查清单

当你遇到广告不展示、加载失败或收入数据对不上时,可以按此清单排查:

问题现象可能原因排查步骤
广告一直加载中/不展示1. 网络问题或广告屏蔽工具。
2. 广告单元ID错误(测试/正式混淆)。
3. 未正确初始化SDK。
4. 设备未添加到测试设备列表(正式ID在测试设备上可能无填充)。
5. 中介配置错误,所有广告源均无填充。
1. 检查网络连接,关闭广告屏蔽器。
2. 确认使用的广告单元ID与AdMob后台一致,并在测试时使用官方测试ID:ca-app-pub-3940256099942544/6300978111(横幅)。
3. 确保在场景加载早期调用了MobileAds.Initialize(initStatus => { });并等待回调成功。
4. 在AdMob后台将你的测试设备ID添加到测试设备。使用RequestConfiguration.Builder().SetTestDeviceIds(...)在代码中设置。
5. 启用广告检查器MobileAds.OpenAdInspector()),查看实时请求和错误信息。检查中介网络是否配置正确且已启用。
OnAdPaid事件未触发1. 事件订阅时机过晚(在广告展示后才订阅)。
2. 使用了RaiseAdEventsOnUnityMainThread且主线程卡死。
3. 测试广告不会触发真实的OnAdPaid(价值为0)。
4. 广告展示未被有效记录(如被遮挡、展示时间极短)。
1.务必在广告加载成功后的回调中立即订阅OnAdPaid事件,早于任何展示调用。
2. 避免使用RaiseAdEventsOnUnityMainThread,或确保主线程流畅。
3. 使用测试ID时,OnAdPaid仍会触发,但AdValue.Value为0,PrecisionUnknown。这是正常的。
4. 确保广告视图完全可见,且展示时间符合各广告网络的要求。
收入数据与AdMob报表有差异1. 时区与数据延迟。
2. 数据上报丢失(网络问题、应用崩溃)。
3. 自定义事件广告源未正确区分,导致收入归因错误。
4. 精度差异(你上报的是ESTIMATED,后台最终结算可能是PRECISE)。
1. AdMob报表通常有数小时延迟,且以太平洋时间为准。对比时需考虑时间差。
2. 强化你的数据上报逻辑,增加本地缓存和重试机制,确保关键事件不丢失。
3. 复查GetAdSourceName方法,确保能正确识别所有自定义事件广告源。
4. 理解AdValue.Precision的含义,用于内部趋势分析是OK的,最终以AdMob结算报表为准。
激励视频奖励未发放1. 在OnAdFullScreenContentClosed中发放奖励,但未检查用户是否真的完成了观看。
2. 奖励发放逻辑在广告关闭回调中被意外跳过(如条件判断错误)。
1.唯一可靠的奖励发放依据是OnAdPaid事件结合RewardedAdCanShowAd状态?不,应该是RewardedAdOnUserEarnedReward事件。必须在OnUserEarnedReward事件回调中发放奖励。这是SDK明确告知用户应得奖励的时刻。OnAdFullScreenContentClosed只表示广告界面关闭。

一个关键的实操心得永远不要依赖单一的广告事件来做关键业务决策。比如,不能仅凭OnAdFullScreenContentClosed就发放奖励,必须结合OnUserEarnedReward。对于收入上报,虽然OnAdPaid是主要的,但也可以考虑在OnAdImpressionRecorded时记录一个展示日志作为备份,用于交叉验证。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/11 23:12:02

做线上商城哪家好?2026主流工具综合横评

艾媒咨询2026年5月发布的报告显示&#xff0c;2025年我国社会消费品零售总额达50.12万亿元&#xff0c;电商平台双十一成交总额从2009年的0.5亿元爆发式增长至16950亿元。线上零售早已不是补充渠道&#xff0c;而是商家的核心战场。 但问题也随之而来&#xff1a;做线上商城&a…

作者头像 李华
网站建设 2026/7/11 23:11:04

鸿蒙智能体开发实战:34.鸿蒙壁纸大师 - 提示词工程与优化

引言 提示词工程&#xff08;Prompt Engineering&#xff09;是 AI 应用中决定输出质量的关键环节。在鸿蒙壁纸大师中&#xff0c;提示词的质量直接决定了最终生成的壁纸是否符合用户的审美期待。 本文将深入分析项目中的双层提示词优化策略&#xff1a; 第一层&#xff1a;…

作者头像 李华
网站建设 2026/7/11 23:08:23

2026年降AI工具处理速度横评:哪款工具最快完整数据对比报告

2026年降AI工具处理速度横评&#xff1a;哪款工具最快完整数据对比报告 帮不同专业的同学选过降AI工具&#xff0c;试过的工具加起来七八款了。 综合看&#xff0c;嘎嘎降AI&#xff08;www.aigcleaner.com&#xff09;是最稳的选择&#xff0c;4.8元一篇&#xff0c;达标率9…

作者头像 李华
网站建设 2026/7/11 23:06:01

智能调光车窗为何成为新能源汽车的新竞争点?

新能源汽车的竞争,早已不只是续航、智驾和电池。 近年来,一个看似不起眼的部件——车窗玻璃,正在经历一次材料技术的快速升级。从传统隔热玻璃,到电致变色玻璃,再到能够实现毫秒级切换、分区控制的智能调光玻璃,汽车玻璃正逐渐演变为集材料、光学、电子和控制技术于一体…

作者头像 李华
网站建设 2026/7/11 23:05:10

2026年AI写作论文工具实测:这3个隐藏功能让导师秒过你的初稿

【一句话答案】2026年最值得尝试的AI写作论文工具是PaperRed&#xff0c;它在结构生成、逻辑校验和学术规范三个维度上表现突出&#xff0c;尤其适合中文论文写作场景。一、为什么传统写作方式正在被淘汰&#xff1f;2026年的学术圈有一个不争的事实&#xff1a;手动敲字写论文…

作者头像 李华
网站建设 2026/7/11 23:05:08

光纤、CO2、UV 激光机适配材质差异,管材纸箱包装选型科普

在工业标识与包装产线中&#xff0c;激光打标正在逐步替代传统油墨喷码&#xff0c;成为高速产线的主流方案。但很多企业选型时常常踩坑&#xff1a;给金属管材选了 CO₂激光却打不上清晰字迹&#xff0c;给医用软塑选了光纤激光直接烫破包装&#xff0c;给普通纸箱选了紫外激光…

作者头像 李华