Unity 接入穿山甲 GroMore 聚合广告完整教程

👉穿山甲官网


适用对象:第一次在 Unity 项目中接入穿山甲(Pangle)GroMore 聚合 SDK 的开发者

覆盖平台:Android + iOS

广告类型:激励视频、插屏(全屏视频)

官方文档参考:GroMore 接入指南本地配置导入方案


目录

  1. [什么是 GroMore 聚合](#什么是 GroMore 聚合)
  2. 接入前准备
  3. [下载并导入 Unity SDK](#下载并导入 Unity SDK)
  4. [GroMore 后台配置](#GroMore 后台配置)
  5. 项目目录规划
  6. [Android 接入](#Android 接入)
  7. [iOS 接入](#iOS 接入)
  8. [SDK 初始化(核心)](#SDK 初始化(核心))
  9. 激励视频接入
  10. 插屏广告接入
  11. 打包与真机测试
  12. 常见错误排查
  13. 上线前检查清单

1. 什么是 GroMore 聚合

穿山甲(Pangle) 是字节跳动的广告平台。

GroMore 是穿山甲提供的**广告聚合(Mediation)**能力:一次请求,自动在多个广告网络(ADN)之间比价/瀑布流,提高填充率和收益。

在 Unity 中接入时,你需要理解三件事:

概念 说明
App ID 应用在穿山甲后台的唯一标识
广告位 ID(CodeId / Rit) 每种广告样式对应一个广告位
聚合瀑布流 一个广告位下挂多个 ADN 代码位,按优先级请求

2. 接入前准备

2.1 开发环境

  • Unity 2020.3 LTS 或更高(建议 LTS)
  • Android:JDK、Android SDK、Gradle
  • iOS:Mac + Xcode 14+、CocoaPods
  • 真机(模拟器无法完整测试广告)

2.2 后台账号

  1. 注册 穿山甲开发者平台
  2. 创建应用,获取 App ID
  3. 创建广告位:
    • 激励视频广告位
    • 插屏/全屏视频广告位
  4. 在 GroMore 后台配置瀑布流(至少接入 2~3 个 ADN)

2.3 记录这些 ID(后面代码要用)

text 复制代码
App ID:           5786***          // 示例,换成你自己的
激励视频广告位:    103867***
插屏广告位:        103866***

3. 下载并导入 Unity SDK

3.1 下载 SDK

从穿山甲官方下载 Unity 版 GroMore 融合 SDK (通常包含 CSJ 文件夹)。

3.2 导入 Unity 工程

将 SDK 文件夹拖入 Assets/ 目录,典型结构如下:

text 复制代码
Assets/
└── CSJ/
    ├── Editor/
    │   └── PangleAdapterScriptsDependencies.xml   # 依赖配置(重要)
    ├── Plugins/
    │   ├── Android/
    │   └── iOS/
    └── Scripts/
        ├── Api/                    # C# API 接口
        └── Implementation/
            ├── Android/
            └── iOS/

导入后确认:

  • 没有编译报错
  • Assets/CSJ/Scripts/Api/ 下有 Pangle.csSDK.cs 等文件
  • 命名空间为 ByteDance.Union

4. GroMore 后台配置

4.1 创建瀑布流

进入 GroMore 后台 → 广告位管理 → 配置瀑布流:

  1. 添加穿山甲自有代码位
  2. 按需添加第三方 ADN(百度、Sigmob、快手、广点通等)
  3. 设置超时时间(建议 20~30 秒)
  4. 设置并行请求数

4.2 导出本地配置文件(强烈建议)

在网络异常、冷启动等场景,线上配置可能拉取失败,导致 40045 等错误。

官方方案:配置拉取失败解决方案

操作步骤:

  1. GroMore 后台 → 应用管理 → 导出配置信息
  2. 得到 site_config_xxxxxxx.json
  3. 放入 Unity 工程:
text 复制代码
Assets/Resources/GroMore/site_config_5786***.json

注意:Android 和 iOS 设置本地配置的方式不同(后面会讲)。

4.3 注册测试设备

开发阶段必须在后台添加测试设备 IDFA/GAID,否则容易出现 10086(全部 ADN 填充失败)。


5. 项目目录规划

建议新建统一的广告管理类,不要散落在各处:

text 复制代码
Assets/
├── Scripts/
│   ├── Ads/
│   │   ├── ATTManager.cs          # iOS ATT 授权
│   │   └── AdManager.cs           # 广告统一管理(本文核心)
│   └── Game/
│       └── GameLaunch.cs          # 启动时初始化广告
├── Resources/
│   └── GroMore/
│       └── site_config_5786***.json
└── PodFileEditor/Editor/          # iOS 构建后处理(可选)
    └── PostProcessBuild_InfoPlist.cs

6. Android 接入

6.1 配置依赖

编辑 Assets/CSJ/Editor/PangleAdapterScriptsDependencies.xml

xml 复制代码
<androidPackages>
  <repositories>
    <repository>https://artifact.bytedance.com/repository/pangle</repository>
  </repositories>
  <!-- 聚合 adapter,穿山甲本体不需要 adapter -->
  <androidPackage spec="com.pangle.cn:mediation-baidu-adapter:9.4503.0"/>
  <androidPackage spec="com.pangle.cn:mediation-sigmob-adapter:4.25.14.0"/>
  <!-- 按需添加其他 ADN -->
</androidPackages>

6.2 Android 本地配置(JSON 字符串)

Android 使用 CustomLocalConfig 直接传 JSON 字符串:

csharp 复制代码
private MediationConfig GetMediationConfigAndroid()
{
    var mediationConfig = new MediationConfig();
    
    // 从 Resources 读取导出的配置文件
    var asset = Resources.Load<TextAsset>("GroMore/site_config_5786***");
    if (asset != null)
    {
        mediationConfig.CustomLocalConfig = asset.text; // 仅 Android
    }
    
    return mediationConfig;
}

6.3 Android 权限

确保 AndroidManifest.xml 包含网络权限(SDK 通常会自动合并):

xml 复制代码
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

7. iOS 接入

iOS 比 Android 多三个关键步骤:ATT 授权、IDFA 配置、本地配置文件路径

7.1 配置 CocoaPods 依赖

同样在 PangleAdapterScriptsDependencies.xml 中配置 iOS Pod:

xml 复制代码
<iosPods>
  <!-- 穿山甲 SDK + 聚合 -->
  <iosPod name="Ads-CN" version="7.6.0.4" 
          bitcodeEnabled="true" minTargetSdk="10.0" 
          subspecs="['CSJMediation-Only']" />
  
  <!-- 第三方 ADN adapter(按需添加) -->
  <iosPod name="BaiduMobAdSDK" version="&lt;=10.050" />
  <iosPod name="GMBaiduAdapter" version="10.050.0" />
</iosPods>

7.2 配置 Info.plist

必须添加 ATT 授权说明,否则无法获取 IDFA:

xml 复制代码
<key>NSUserTrackingUsageDescription</key>
<string>为了向您提供更好的广告体验,我们需要获取您的设备标识符</string>

可用 Unity 构建后处理脚本自动写入(PostProcessBuild_InfoPlist.cs):

csharp 复制代码
[PostProcessBuild(999)]
public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
{
    if (target != BuildTarget.iOS) return;

    string plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
    PlistDocument plist = new PlistDocument();
    plist.ReadFromFile(plistPath);

    plist.root.SetString("NSUserTrackingUsageDescription",
        "为了向您提供更好的广告体验,我们需要获取您的设备标识符");

    plist.WriteToFile(plistPath);
}

7.3 ATT 授权管理器

iOS 14.5+ 必须先弹 ATT 授权窗,再初始化广告 SDK:

csharp 复制代码
// ATTManager.cs(简化示例)
public class ATTManager : MonoBehaviour
{
#if UNITY_IOS && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern void _RequestTrackingAuthorization(
        string callbackTarget, string callbackMethod);

    public void RequestAuthorization(Action<int> callback)
    {
        _authorizationCallback = callback;
        _RequestTrackingAuthorization(gameObject.name, "OnATTCallback");
    }

    // 由 iOS 原生回调
    private void OnATTCallback(string statusCode)
    {
        _authorizationCallback?.Invoke(int.Parse(statusCode));
    }
#endif
}

ATT 状态说明:

状态 含义 处理方式
Authorized (3) 用户同意 使用真实 IDFA
Denied (2) 用户拒绝 使用自定义设备标识
NotDetermined (0) 未选择 使用自定义设备标识

7.4 配置自定义 IDFA

必须在 Pangle.Init() 之前调用

csharp 复制代码
public bool ConfigureCustomIdfa()
{
    var config = PangleConfiguration.CreateInstance();
    config.customIdfa = GetCustomIdfa(); // 根据 ATT 状态返回 IDFA 或设备标识
    return true;
}

private string GetCustomIdfa()
{
#if UNITY_IOS && !UNITY_EDITOR
  if (ATTManager.Instance.IsAuthorized())
      return GetRealIdfa();          // 真实 IDFA
  else
      return SystemInfo.deviceUniqueIdentifier; // 兜底标识
#else
  return SystemInfo.deviceUniqueIdentifier;
#endif
}

7.5 iOS 本地配置(文件路径方式)

iOS 不支持 CustomLocalConfig 字符串,必须用文件路径

官方原生写法:

objc 复制代码
configuration.mediation.advanceSDKConfigPath = 
    [[NSBundle mainBundle] pathForResource:@"GroMore-config-ios" ofType:@"json"];

Unity 中需要通过 DllImport 调用原生方法。

第一步:在 PangleConfiguration.mm 添加原生函数

objc 复制代码
void UnionPlatform_setAdvanceSDKConfigPath(const char* path) {
    if (path == NULL) return;
    NSString *oc_path = [NSString stringWithUTF8String:path];
    BUAdSDKConfiguration *configuration = [BUAdSDKConfiguration configuration];
    configuration.mediation.advanceSDKConfigPath = oc_path;
    NSLog(@"CSJM_Unity setAdvanceSDKConfigPath: %@", oc_path);
}

第二步:在 C# 中调用(必须在 Pangle.Init() 之前)

csharp 复制代码
#if !UNITY_EDITOR && UNITY_IOS
[DllImport("__Internal")]
private static extern void UnionPlatform_setAdvanceSDKConfigPath(string path);

private void SetupGroMoreLocalConfig()
{
    var asset = Resources.Load<TextAsset>("GroMore/site_config_5786***");
    if (asset == null) return;

    // 写入持久化目录(iOS 需要真实文件路径)
    string path = Path.Combine(Application.persistentDataPath, "site_config.json");
    File.WriteAllText(path, asset.text);

    UnionPlatform_setAdvanceSDKConfigPath(path);
    Debug.Log($"✅ GroMore本地配置已设置: {path}");
}
#endif

8. SDK 初始化(核心)

8.1 正确的初始化时序

这是整个接入最关键的部分,顺序不能错:

text 复制代码
① ATT 授权弹窗(iOS)
      ↓
② ConfigureCustomIdfa()(iOS,Pangle.Init 之前)
      ↓
③ SetupGroMoreLocalConfig()(iOS,Pangle.Init 之前)
      ↓
④ Pangle.Init(configuration)   ← 预初始化
      ↓
⑤ Pangle.Start(callback)        ← 正式启动
      ↓
⑥ 回调 success=true 后,延迟 2 秒
      ↓
⑦ 预加载激励视频 + 插屏广告

8.2 启动入口(GameLaunch.cs)

csharp 复制代码
private void Start()
{
#if UNITY_IOS && !UNITY_EDITOR
    InitializeIOSPlatform();
#endif
}

private void InitializeIOSPlatform()
{
    // 第一步:ATT 授权
    ATTManager.Instance.RequestAuthorizationOnLaunch(status =>
    {
        // 第二步:配置 IDFA
        AdManager.Instance.ConfigureCustomIdfa();

        // 第三步:初始化 SDK
        AdManager.Instance.InitializeCSJSDKAfterATT();
    });
}

8.3 构建 SDK 配置

csharp 复制代码
private const string APP_ID = "5786***";       // 换成你的
private const string APP_NAME = "你的应用名";

private SDKConfiguration BuildSdkConfiguration()
{
    var builder = new SDKConfiguration.Builder()
        .SetAppId(APP_ID)
        .SetAppName(APP_NAME)
        .SetUseMediation(true)          // 开启 GroMore 聚合
        .SetDebug(true)                 // 开发阶段开启,上线关闭
        .SetThemeStatus(0)
        .SetSupportMultiProcesse(false)
        .SetPrivacyConfigurationn(GetPrivacyConfiguration());

#if UNITY_ANDROID
    builder.SetMediationConfig(GetMediationConfigAndroid());
#endif

    return builder.Build();
}

private PrivacyConfiguration GetPrivacyConfiguration()
{
    var config = new PrivacyConfiguration();
    config.CanUseLocation = false;
    config.CanUsePhoneState = false;
    config.CanUseWifiState = false;
    config.CanUseWriteExternal = false;
    config.CanUseAndroidId = false;

    config.MediationPrivacyConfig = new MediationPrivacyConfig();
    config.MediationPrivacyConfig.LimitPersonalAds = false;
    config.MediationPrivacyConfig.ProgrammaticRecommend = true;

    return config;
}

8.4 执行初始化

csharp 复制代码
private bool _csjInitialized;

public void InitializeCSJSDKAfterATT()
{
    if (_csjInitialized) return;

#if !UNITY_EDITOR && UNITY_IOS
    SetupGroMoreLocalConfig();  // 必须在 Init 之前
#endif

    Pangle.Init(BuildSdkConfiguration());
    Pangle.Start(OnSdkInitCallback);
}

private void OnSdkInitCallback(bool success, string message)
{
    Debug.Log($"SDK初始化: success={success}, message={message}");
    Debug.Log($"IsSdkReady: {Pangle.IsSdkReady()}");

    if (!success) return;

    _csjInitialized = true;
    StartCoroutine(DelayedInitialAdLoad());
}

private IEnumerator DelayedInitialAdLoad()
{
    yield return new WaitForSeconds(2f); // 等 SDK 完全就绪
    LoadRewardedAd();
    LoadInterstitialAd();
}

9. 激励视频接入

9.1 官方推荐的完整流程

text 复制代码
LoadRewardVideoAd()
    → OnRewardVideoAdLoad()     // 加载成功,但还不能展示
    → OnRewardVideoCached()     // 缓存完成,可以展示了
    → IsReady() 检查
    → ShowRewardVideoAd()
    → OnRewardArrived()         // 发放奖励
    → OnAdClose()               // 关闭,销毁对象,预加载下一条

9.2 构造 AdSlot

csharp 复制代码
private AdSlot BuildRewardAdSlot()
{
    return new AdSlot.Builder()
        .SetCodeId(REWARDED_AD_UNIT_ID)
        .SetOrientation(AdOrientation.Horizontal)  // 横屏游戏用 Horizontal
        .SetMediationAdSlot(
            new MediationAdSlot.Builder()
                .SetMuted(false)
                .Build())
        .Build();
}

9.3 加载广告

csharp 复制代码
private RewardVideoAd _rewardedAd;
private bool _rewardedCached;

private void LoadRewardedAd()
{
    // 官方要求:每次加载前销毁旧对象
    if (_rewardedAd != null)
    {
        _rewardedAd.Dispose();
        _rewardedAd = null;
    }
    _rewardedCached = false;

    SDK.CreateAdNative().LoadRewardVideoAd(
        BuildRewardAdSlot(),
        new RewardedAdListener(this));
}

9.4 加载监听器

csharp 复制代码
private class RewardedAdListener : IRewardVideoAdListener
{
    private readonly AdManager _manager;
    public RewardedAdListener(AdManager manager) => _manager = manager;

    public void OnError(int code, string message)
    {
        Debug.LogError($"激励视频加载失败: {code} {message}");
        // 官方建议:仅重试 1 次
        _manager.RetryLoadRewarded();
    }

    public void OnRewardVideoAdLoad(RewardVideoAd ad)
    {
        Debug.Log("激励视频加载成功,等待缓存...");
        _manager._rewardedAd = ad;
        // 注意:这里不要标记为 ready!
    }

    public void OnRewardVideoCached()
    {
        Debug.Log("激励视频缓存完成,可以展示");
        _manager._rewardedCached = true;
    }

    public void OnRewardVideoCached(RewardVideoAd ad)
    {
        _manager._rewardedAd = ad;
        _manager._rewardedCached = true;
    }
}

9.5 展示前检查(重要)

csharp 复制代码
private bool IsRewardVideoReady()
{
    if (_rewardedAd == null || !_rewardedCached) return false;

    var manager = _rewardedAd.GetMediationManager();
    return manager != null && manager.IsReady();
}

public bool ShowRewardedAd()
{
    if (!IsRewardVideoReady())
    {
        Debug.LogWarning("激励视频未就绪");
        LoadRewardedAd(); // 触发加载
        return false;
    }

    _rewardedAd.SetRewardAdInteractionListener(new RewardedInteractionListener(this));
    _rewardedAd.ShowRewardVideoAd();
    return true;
}

尊敬的主人,发奖逻辑已按你们项目里的最新实现整理如下,可直接替换博客 9.6 展示监听器 一节。


9.6 展示监听器(发奖逻辑)

激励视频的发奖不要写在 OnRewardArrived 里直接发,推荐采用「先记录、关闭时统一处理」的模式,避免广告界面还没关完,游戏就已经恢复运行。

设计原则

回调 职责
OnRewardArrived 只记录 _rewardArrived_rewardValid不发奖
OnAdClose 统一发奖 / 通知未完成,并做埋点

完整示例

csharp 复制代码
private class RewardedInteractionListener : IRewardAdInteractionListener
{
    private readonly AdManager _manager;

    // 是否已收到 OnRewardArrived;未收到表示用户极早退出
    private bool _rewardArrived;
    // OnRewardArrived 返回的奖励是否有效;仅在 _rewardArrived 为 true 时有意义
    private bool _rewardValid;

    public RewardedInteractionListener(AdManager manager) => _manager = manager;

    public void OnAdShow()
    {
        _rewardArrived = false;
        _rewardValid = false;
        // 埋点:广告开始展示
        Analytics.Log("Ads_re_Show");
    }

  public void OnRewardArrived(bool isRewardValid, int rewardType, IRewardBundleModel extraInfo)
    {
        Debug.Log($"OnRewardArrived verify:{isRewardValid} rewardType:{rewardType}");

        // 仅记录奖励结果,等 OnAdClose 再发奖/通知,避免广告未关时游戏已恢复
        _rewardArrived = true;
        _rewardValid = isRewardValid;
    }

    public void OnAdClose()
    {
        _manager._rewardedShowing = false;

        if (!_rewardArrived)
        {
            // 极早退出:连奖励回调都没收到
            Debug.Log("广告关闭时未收到奖励回调,视为未完整播放");
            _manager.OnRewardAdNoCompleteClose?.Invoke();
        }
        else if (_rewardValid)
        {
            // 广告界面关闭后再发放奖励,避免关闭动画期间游戏已恢复运行
            _manager.GrantRewarded();
        }
        else
        {
            // 收到了回调但 isRewardValid=false(未看完)
            Debug.Log("激励视频未播放完成,不发放奖励");
            _manager.OnRewardAdNoCompleteClose?.Invoke();
        }

        // 埋点
        Analytics.Log("Ads_re_Close");

        // 销毁并预加载下一条
        _manager._rewardedAd?.Dispose();
        _manager._rewardedAd = null;
        _manager.LoadRewardedAd();
    }

    public void OnVideoComplete() { }
    public void OnVideoError()    { _manager.HandleRewardAdShowOrLoadFailure(); }
    public void OnVideoSkip()     { }
    public void OnAdVideoBarClick() { }
}

三种关闭场景

text 复制代码
场景 A:用户极早退出(未触发 OnRewardArrived)
  OnAdClose → !_rewardArrived → 通知未完成

场景 B:正常看完(OnRewardArrived 且 isRewardValid=true)
  OnRewardArrived → 记录 _rewardArrived=true, _rewardValid=true
  OnAdClose       → GrantRewarded() 发奖

场景 C:中途跳过(OnRewardArrived 但 isRewardValid=false)
  OnRewardArrived → 记录 _rewardArrived=true, _rewardValid=false
  OnAdClose       → 通知未完成,不发奖

10. 插屏广告接入

插屏使用 全屏视频广告(FullScreenVideoAd),流程与激励视频几乎相同。

10.1 构造 AdSlot

csharp 复制代码
private AdSlot BuildInterstitialAdSlot()
{
    return new AdSlot.Builder()
        .SetCodeId(INTERSTITIAL_AD_UNIT_ID)
        .SetOrientation(AdOrientation.Horizontal)
        .SetMediationAdSlot(
            new MediationAdSlot.Builder()
                .SetMuted(false)
                .Build())
        .Build();
}

10.2 加载与展示

csharp 复制代码
private FullScreenVideoAd _interstitialAd;
private bool _interstitialCached;

private void LoadInterstitialAd()
{
    if (_interstitialAd != null)
    {
        _interstitialAd.Dispose();
        _interstitialAd = null;
    }
    _interstitialCached = false;

    SDK.CreateAdNative().LoadFullScreenVideoAd(
        BuildInterstitialAdSlot(),
        new InterstitialAdListener(this));
}

private bool IsInterstitialReady()
{
    if (_interstitialAd == null || !_interstitialCached) return false;
    var manager = _interstitialAd.GetMediationManager();
    return manager != null && manager.IsReady();
}

public bool ShowInterstitialAd()
{
    if (!IsInterstitialReady())
    {
        LoadInterstitialAd();
        return false;
    }

    _interstitialAd.SetFullScreenVideoAdInteractionListener(
        new InterstitialInteractionListener(this));
    _interstitialAd.ShowFullScreenVideoAd();
    return true;
}

10.3 关闭后预加载

csharp 复制代码
public void OnAdClose()
{
    _manager._interstitialAd?.Dispose();
    _manager._interstitialAd = null;
    _manager.LoadInterstitialAd(); // 预加载下一条
}

11. 打包与真机测试

11.1 Android 打包

  1. File → Build Settings → Android
  2. 确认 PangleAdapterScriptsDependencies.xml 中的依赖已解析
  3. 导出 APK/AAB,安装到真机测试

11.2 iOS 打包

  1. File → Build Settings → iOS → Build
  2. 用 Xcode 打开工程
  3. 确认 pod install 成功(Unity 会自动触发)
  4. 在 Xcode 中编译安装到真机

11.3 测试日志检查点

正常启动应看到如下日志顺序:

text 复制代码
[ATTManager] 开始请求ATT授权
[ATTManager] ATT授权结果: Authorized
✅ 自定义IDFA配置成功
✅ GroMore本地配置已设置(路径: ...)
========== 开始调用Pangle.Init() ==========
SDK初始化: success=True
SDK初始化成功,2秒后开始加载广告
开始加载激励视频广告
激励视频缓存完成,可以展示        ← 关键!

11.4 注册测试设备

日志中找到 IDFA,例如:

text 复制代码
真实IDFA: B251761C-CCFC-4D9E-89E1-184032BE3624

到穿山甲后台 → 测试工具 → 添加此 IDFA,等 5 分钟后重新测试。


12. 常见错误排查

错误码 含义 解决方案
40045 找不到广告位配置(rit) ① 检查广告位 ID 是否正确 ② iOS 设置本地配置文件 ③ 检查网络是否正常
10086 瀑布流全部 ADN 填充失败 ① 注册测试设备 IDFA ② 检查瀑布流 ADN 配置 ③ 增加 ADN 数量
10010 请求超时 ① 延长瀑布流超时时长 ② 减少瀑布流层数 ③ 检查网络
SDK 未初始化 初始化时序错误 确认 ATT → IDFA → Init → Start 顺序
广告加载成功但不展示 未等 Cached 回调 必须在 OnRewardVideoCached 后才展示
iOS 审核广告不显示 审核环境无广告填充 开启审核容错模式,失败时直接发奖励

调试技巧:打印聚合加载详情

csharp 复制代码
private void LogMediationLoadInfo(RewardVideoAd ad, string scene)
{
    var manager = ad?.GetMediationManager();
    if (manager == null) return;

    foreach (var info in manager.GetAdLoadInfo())
    {
        Debug.Log($"{scene} LoadInfo: {info}");
        // 可以看到每个 ADN 的失败原因
    }
}

OnErrorOnRewardVideoCached 中调用,可精确定位哪个 ADN 出了问题。


13. 上线前检查清单

代码层面

  • SetDebug(false) 关闭调试日志
  • 广告位 ID 换成正式环境的 ID
  • site_config_xxx.json 是最新导出的配置
  • 移除或关闭审核容错模式(REVIEW_MODE_ENABLED = false
  • 确认 ATT 授权弹窗文案符合苹果审核要求

后台层面

  • 瀑布流至少接入 3 个 ADN
  • 各 ADN 的 App ID / Slot ID 配置正确
  • 测试设备 IDFA 已注册(测试阶段)
  • Bundle ID / 包名与后台一致

iOS 审核

  • NSUserTrackingUsageDescription 已配置
  • 广告失败时有合理的用户体验(不能太突兀)
  • 提审期间可开启审核容错:广告失败时直接发放奖励,保证审核员能体验完整流程

附录:完整初始化时序图

text 复制代码
App 启动
  │
  ├─ [iOS] ATTManager.RequestAuthorization()
  │         └─ 用户选择 允许/拒绝
  │
  ├─ [iOS] ConfigureCustomIdfa()
  │         └─ PangleConfiguration.customIdfa = IDFA
  │
  ├─ [iOS] SetupGroMoreLocalConfig()
  │         └─ UnionPlatform_setAdvanceSDKConfigPath(path)
  │
  ├─ Pangle.Init(SDKConfiguration)
  │         └─ 预初始化(合规要求,此时不联网)
  │
  ├─ Pangle.Start(callback)
  │         └─ 正式启动,拉取线上配置
  │         └─ callback(success=true)
  │
  ├─ 延迟 2 秒
  │
  ├─ LoadRewardedAd()      ← 预加载激励
  └─ LoadInterstitialAd()  ← 预加载插屏

用户点击"看广告"
  │
  ├─ IsRewardVideoReady() ?
  │     ├─ false → 触发加载,显示等待 UI
  │     └─ true  → ShowRewardVideoAd()
  │
  ├─ OnAdShow()
  ├─ OnRewardArrived() → 标记发放奖励
  └─ OnAdClose()奖励发放 → Dispose() → LoadRewardedAd()(预加载下一条)

总结

接入穿山甲 GroMore 聚合的核心要点:

  1. 时序:ATT → IDFA → 本地配置 → Init → Start → 预加载
  2. 展示 :必须等 OnCached + IsReady() 才能展示
  3. 生命周期 :每次加载前 Dispose() 旧对象,关闭后预加载新对象
  4. 本地配置:Android 用 JSON 字符串,iOS 用文件路径
  5. 测试 :必须注册测试设备,否则大量 10086 错误

按本文步骤操作,新手也能在 Unity 项目中独立完成接入。遇到问题时,优先查看 getAdLoadInfoList 的聚合加载详情,比盲目重试有效得多。


本文基于 Unity + 穿山甲 GroMore SDK 7.x 版本实战经验整理。SDK 版本更新时请以官方文档为准。