简介:面向使用 Unity 引擎开发 iOS 应用、希望在应用内嵌入原生广告以提升变现能力的开发者,这套资料聚焦 AppLovin MAX 的 Native 广告接入实践,覆盖从 SDK 导入、广告位配置到原生广告视图渲染与事件回调的完整链路,适合已具备一定 Unity 与 iOS 基础、正着手广告商业化改造的中级开发者参考。资源包共 58 个文件,约 367KB,以 png 图片素材、meta 配置、json 描述文件为主,另含 h、m、mm、swift 等源码与模块文件、nib 界面文件、MaxNativeAdFramework 框架及 Assets.car 素材包,构成可直接对照的原生广告展示工程结构。已有 106 人学习。通过其中 NativeManualAdView 与 SwiftBufferManager 等实现,读者可以了解原生广告在 Unity 与 iOS 原生层之间如何传递数据、如何布局广告卡片与按钮图标资源,并借鉴广告加载失败、展示完成、点击等回调的处理思路,减少接入调试中的反复试错,快速把原生广告融入现有界面设计。
1. 从一次 Native 广告空白说起:Unity iOS 接入 MAX 的版本坑
某开发者把 Unity 工程导出成 Xcode 工程,Native 广告的回调日志明明打印了 loaded,真机上却只剩一块空白区域。查到最后不是广告位没量,也不是代码没执行,而是 Unity iOS 侧的最低系统版本、Info.plist 里的 ATT 描述和 Native 广告视图绑定三处没有对齐。这类问题在 Unity iOS 接入 AppLovin MAX Native 广告时很常见,尤其是从插屏、激励视频转过来的工程,容易把 Native 当成“换一个广告位 ID 就能跑”的格式。装不好时最典型的后果是:编译能过、日志有回调、审核也能过,但用户看不到广告,排查起来像黑匣子。这套资源适合正在用 Unity 做 iOS 客户端、需要把 Native 广告放进列表流或自定义卡片的开发者,也适合接手别人工程、需要把 MAX 配置重新梳理一遍的运维式开发者。下面按我实际落地的顺序,从工程版本、iOS 原生配置、Native 代码路径到排查验证,一步步走完。
2. Unity 工程准备:2021.3 LTS 与 iOS 15 基线怎么对齐
2.1 版本选择:Unity、Xcode、CocoaPods 三者怎么对齐
Unity iOS 接 MAX Native,第一件事不是导包,而是把工具链版本定死。我的习惯是先用终端把本机环境打出来,避免“工程能打开但构建脚本跑不通”的翻车。下面这条命令不复杂,但能一次看清 macOS、Xcode、CocoaPods 的版本,后面出问题时有对照基线。
# 在 macOS 终端确认本机工具链,建议把输出贴到工程 README 里 sw_vers xcodebuild -version pod --version ruby --versionsw_vers看 macOS 大版本,xcodebuild -version看 Xcode 和 iOS SDK,pod --version看 CocoaPods。这里不需要追求最新,追新反而容易踩到插件还没适配的坑。我一般把 Unity 2021.3 LTS 作为基线,Xcode 14 及以上、CocoaPods 1.11 及以上作为常见组合,最低 iOS 版本设到 15.0。这样做的好处是 PrivacyInfo、ATT、SKAdNetwork 这些 iOS 侧配置有稳定落点,旧系统兼容分支也少。Unity 2020 及更早版本不是不能做,但导出工程后隐私清单和 Pod 依赖经常要手工补,熟手能处理,新手容易在 Xcode 阶段卡住。
| 项目 | 建议基线 | 说明 |
|---|---|---|
| Unity | 2021.3 LTS | iOS 构建、隐私清单和 Pod 导出更稳 |
| Xcode | 14 及以上 | 能覆盖 iOS 15+ 的构建与调试 |
| iOS 最低版本 | 15.0 | 减少旧系统兼容分支,和广告 SDK 常见要求对齐 |
| CocoaPods | 1.11 及以上 | 处理 MAX 与 mediation adapter 依赖 |
| 脚本后端 | IL2CPP | iOS 发布必选,避免 Mono 构建差异 |
| 目标架构 | ARM64 | 真机与上架都走 ARM64 |
版本定完后,别急着把 MAX 包拖进工程。先在 Unity 里把 Player Settings 里的包名、方向、脚本后端、目标架构改好,再导包。顺序反了,后面 Xcode 里会出现一堆“看起来是广告 SDK 的问题,实际是 Unity 工程设置没生效”的假故障。
2.2 新建或改造工程:包名、方向、脚本后端
我一般先检查 Player Settings 的 iOS 页签,尤其是 Bundle Identifier、Target minimum iOS version、Scripting Backend、Target Architectures 这四项。包名不要用默认的com.DefaultCompany.ProjectName,否则真机调试和后台广告位白名单容易对不上。方向按业务锁,Native 广告卡片通常放在竖屏列表里,但广告视图本身要能跟随父容器宽度,不要写死像素。
在 Unity 里可以用编辑器脚本批量确认关键设置,避免手点漏项。下面这段只做检查和日志输出,不改工程,适合接手旧项目时先跑一遍。
// 放在 Editor 目录下,菜单栏执行 Tools/Check iOS Ad Settings using UnityEditor; using UnityEngine; public class CheckiOSAdSettings : EditorWindow { [MenuItem("Tools/Check iOS Ad Settings")] public static void Check() { var bundleId = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS); var iOSVersion = PlayerSettings.iOS.targetOSVersionString; var backend = PlayerSettings.GetScriptingBackend(BuildTargetGroup.iOS); var arch = PlayerSettings.GetArchitecture(BuildTargetGroup.iOS); Debug.Log($"[iOS Check] BundleId={bundleId}"); Debug.Log($"[iOS Check] MinOS={iOSVersion}"); Debug.Log($"[iOS Check] Backend={backend}"); Debug.Log($"[iOS Check] Arch={arch}"); if (backend != ScriptingImplementation.IL2CPP) Debug.LogWarning("[iOS Check] iOS 发布建议使用 IL2CPP"); if (!iOSVersion.StartsWith("15")) Debug.LogWarning("[iOS Check] 当前最低版本不是 15.x,请确认是否需要下调"); } }这段代码读的是PlayerSettings里的实际值,不是面板上看到的缓存值。GetApplicationIdentifier对应包名,targetOSVersionString对应最低 iOS 版本,GetScriptingBackend和GetArchitecture对应脚本后端与架构。日志里出现Mono或ARMv7就要停下来改,iOS 发布链路不要带这些组合。Debug.LogWarning不是报错,但它能提醒你:版本基线和团队约定不一致时,先确认原因再继续。
改完设置后,建议直接做一次空包构建,确认 Xcode 工程能生成、能编译、能装真机。不要等 MAX 包导进去再构建,否则编译错误会混在一起,分不清是 Unity 设置问题还是 SDK 依赖问题。空包构建通过后,再进入导入 MAX Unity 包这一步。
2.3 导入 MAX Unity 包与 Native 依赖
MAX 的 Unity 包一般从后台下载,拿到的是压缩包或.unitypackage。我习惯先在终端看一眼包结构,确认里面有没有 Native 相关目录和 iOS 依赖说明,再回 Unity 导入。下面这条命令只列文件,不修改包内容。
# 把下载到的 MAX Unity 包放在 Downloads 下,先确认包结构 unzip -l ~/Downloads/MAXUnityPackage.zip | head -40unzip -l只列目录,head -40防止输出太长。看到AppLovinMAX、Plugins/iOS、Samples这类目录,说明包结构正常;如果只有几个 dll,没有 iOS 原生依赖,先别导,重新确认下载的是不是 Unity 版本。导入时走Assets > Import Package > Custom Package,把 Native 示例场景一并勾上。Native 广告和插屏不同,MAX 只负责素材和加载,展示视图要你自己绑定,示例场景里的MaxNativeAdView预制体是最快的参照。
导入后先别改代码,直接打开示例场景跑一次初始化。如果示例场景能出日志,说明包和基础依赖没问题;如果示例场景都报错,先查 Unity 版本和包版本是否匹配,不要一开始就怀疑广告位。Native 广告位还要在 MAX 后台创建,类型选 Native,拿到广告位 ID 后填到自己的配置里。广告位 ID 不要写死在多个脚本里,我一般放一个ScriptableObject或配置类,后面换测试位、正式位不用全局搜索。
提示:导入 MAX 包后先做一次 iOS 空包构建,再改业务代码。构建失败时只看 Xcode 第一处错误,后面的连锁报错大多是第一处引起的。
3. iOS 原生配置:Info.plist、ATT 与 SKAdNetwork 一次性清单
3.1 Info.plist 关键键值:ATT、广告标识、网络权限
Unity 导出 Xcode 工程后,很多广告问题不是 C# 层,而是Info.plist少键。ATT 描述缺失时,真机上可能不弹授权框,广告请求能发出去但归因和填充都不稳定。下面这段 XML 是常见落点,放在Info.plist的顶层字典里,键名不要改。
<!-- Info.plist 片段:ATT 描述与广告标识相关配置 --> <key>NSUserTrackingUsageDescription</key> <string>用于向你展示更相关的广告,并衡量广告效果。</string> <key>NSAppTransportSecurity</key> <dict> <!-- 不要为了省事全局放开,按广告 SDK 要求配置例外域 --> <key>NSAllowsArbitraryLoads</key> <false/> </dict>NSUserTrackingUsageDescription是 ATT 弹窗文案,必须写清楚用途,不要写“需要权限”这种空话。NSAllowsArbitraryLoads我一般保持false,广告 SDK 需要的例外域按后台文档补,不要全局放开,否则审核和后续安全排查都麻烦。如果工程里已经有这个键,先合并,不要重复写同名 key,Info.plist重复键的行为不稳定,容易出现在 Xcode 里看着有、运行时读不到的情况。
改完plutil检查一遍语法,比在 Xcode 里肉眼找括号可靠:
# 在 Xcode 工程目录检查 Info.plist 语法 plutil -lint Unity-iPhone/Info.plistplutil -lint只做语法校验,输出OK说明 plist 结构没坏。它不检查键值是否符合广告 SDK 要求,所以还要对照 MAX 后台的 iOS 配置清单逐项过。ATT 弹窗的时机也要注意:不要在一进游戏、还没初始化 MAX 的时候弹,用户容易拒绝;也不要在广告加载回调里反复弹。常见做法是初始化完成后,在合适的引导页请求一次,拒绝后不要循环骚扰。
3.2 SKAdNetwork 与隐私清单:别等审核才补
SKAdNetwork 是 iOS 广告归因绕不开的配置,MAX 后台会给出需要补的列表,通常是一组SKAdNetworkIdentifier。我不建议手写猜测,也不建议只放一两个占位,正确做法是按后台生成的列表逐项合并到Info.plist的SKAdNetworkItems数组里。下面只给结构,具体值从后台拿。
<!-- Info.plist 片段:SKAdNetworkItems 结构 --> <key>SKAdNetworkItems</key> <array> <dict> <key>SKAdNetworkIdentifier</key> <string>按 MAX 后台生成的列表逐项补齐</string> </dict> <!-- 每一项一个 dict,不要把所有 ID 塞进一个 string --> </array>这个数组的每一项是一个字典,键是SKAdNetworkIdentifier,值是后台给的标识。合并时注意去重,重复项不会让广告更好,反而增加维护成本。隐私清单PrivacyInfo.xcprivacy也要检查,Unity 导出工程和第三方 SDK 可能各带一份,Xcode 里要确认最终打包进了主 Target。审核因为隐私清单缺失被拒时,日志里通常不会告诉你缺哪一项,只能对照 SDK 文档和 Xcode 的 Copy Bundle Resources 查。
注意:SKAdNetwork 列表和隐私清单都属于“上线前不疼、审核时疼”的配置。导入 MAX 包后就补齐,不要等提审前一天再补。
3.3 Podfile 与 Xcode 构建设置:bitcode、架构、Linker Flags
Unity 导出 iOS 工程后,如果启用了 CocoaPods,目录里会有Podfile。MAX 和 mediation adapter 的依赖通常由导出流程生成,但接手旧工程时经常看到有人手写死版本,最后 pod install 冲突。我的做法是保留生成结构,只检查平台版本和 Target,不轻易锁死 SDK 小版本。
# Podfile 片段:确认平台版本和 Target,不要盲目锁死 SDK 小版本 platform :ios, '15.0' use_frameworks! target 'UnityFramework' do # MAX 主 SDK 与 adapter 以导出工程生成的为准 # 如果手工补 pod,先和 MAX 后台的 iOS 配置清单核对 endplatform :ios, '15.0'要和 Player Settings 里的最低版本一致,否则 Xcode 会警告部署版本不匹配。use_frameworks!是 Swift 与动态框架常见配置,MAX 相关依赖按导出工程生成即可。手写pod 'AppLovinSDK', 'x.y.z'这种锁死小版本的做法,除非团队有明确兼容矩阵,否则不建议。改完 Podfile 后在Builds/iOS目录执行安装:
cd Builds/iOS pod install --repo-update open Unity-iPhone.xcworkspacepod install会生成.xcworkspace,之后打开工程要用 workspace,不要再用.xcodeproj,否则 Pod 依赖不会参与编译。--repo-update在本地索引太旧时有用,但不要每次构建都加,网络慢的时候容易卡住。Xcode 里还要确认Other Linker Flags带-ObjC,广告 SDK 的 Objective-C 分类需要它;Enable Bitcode设为NO,现在 iOS 发布链路基本不再需要 bitcode。可以用xcodebuild -showBuildSettings快速核对:
# 检查 Linker Flags、Bitcode、最低版本是否落到构建配置里 xcodebuild -showBuildSettings \ -workspace Unity-iPhone.xcworkspace \ -scheme Unity-iPhone | grep -E "OTHER_LDFLAGS|ENABLE_BITCODE|IPHONEOS_DEPLOYMENT_TARGET"输出里OTHER_LDFLAGS应该能看到-ObjC,ENABLE_BITCODE为NO,IPHONEOS_DEPLOYMENT_TARGET和 Podfile 一致。grep只是过滤关键行,不要被其他配置干扰。如果这里不对,先去 Build Settings 改,不要急着改 C# 广告代码,Native 广告不展示很多时候是链接阶段就把分类丢了。
4. Native 广告位落地:从 MAX 初始化到自定义布局的代码路径
4.1 初始化 MAX SDK:SdkKey、Mediation 调试模式
MAX 初始化要在广告加载之前完成,且只做一次。我一般写一个常驻的MonoBehaviour,把 SdkKey 和广告位 ID 放在 Inspector 里,避免硬编码。初始化回调里再决定是否加载 Native,不要Start里初始化完立刻LoadAd,SDK 还没准备好时请求容易失败。
using UnityEngine; public class MaxAdsBootstrap : MonoBehaviour { [SerializeField] private string maxSdkKey = "YOUR_MAX_SDK_KEY"; [SerializeField] private string nativeAdUnitId = "YOUR_NATIVE_AD_UNIT_ID"; private bool sdkReady; private void Start() { MaxSdk.SetSdkKey(maxSdkKey); MaxSdk.InitializeSdk(); MaxSdkCallbacks.OnSdkInitializedEvent += config => { sdkReady = true; Debug.Log($"[MAX] init done, mediated={config.IsMediatedNetworkInitialized}"); // 初始化完成后再通知业务层加载 Native }; } public bool IsReady() => sdkReady; }MaxSdk.SetSdkKey填 MAX 后台的 SDK Key,InitializeSdk触发初始化。OnSdkInitializedEvent里的config.IsMediatedNetworkInitialized用来判断 mediation 网络是否就绪,不是所有广告位都必须等它为 true,但 Native 依赖多个 adapter 时,先看这个值能少走弯路。sdkReady只做业务层开关,不要用它替代广告加载回调。调试阶段可以调MaxSdk.ShowMediationDebugger()看各网络状态,这个方法在真机上用,模拟器上信息不全。
提示:SdkKey 和广告位 ID 分开发配置。SdkKey 是应用级,广告位 ID 是展示级,混在一个字符串里后面换测试位容易出错。
4.2 创建 Native 广告位与自定义布局
Native 广告不是插屏,MAX 给的是素材,展示视图要你自己搭。常见布局是:图标、标题、正文、CTA 按钮、MediaView。Unity 侧一般用示例里的MaxNativeAdView预制体,在 Inspector 里把 Text、Image、Button 绑到对应字段,再把预制体放进列表项。不要用固定高度写死,Native 素材的 MediaView 比例会变,父容器最好用LayoutElement或ContentSizeFitter自适应。
创建广告位时,先在 MAX 后台建 Native 类型,拿到广告位 ID。测试阶段用后台的测试模式,不要拿正式广告位反复刷,容易影响数据。加载器我一般封装成一个类,避免每个列表项都 new 一个 loader。下面按 NativeAdLoader + NativeAdView 的典型调用写,导入包后先对照Samples目录里的 Native 场景确认 API 名称,不同插件小版本可能有差异。
using UnityEngine; public class NativeAdPresenter : MonoBehaviour { [SerializeField] private string nativeAdUnitId = "YOUR_NATIVE_AD_UNIT_ID"; [SerializeField] private MaxNativeAdView nativeAdView; // 示例预制体上的视图组件 private MaxNativeAdLoader nativeLoader; public void Load() { if (nativeLoader == null) { nativeLoader = new MaxNativeAdLoader(nativeAdUnitId, MaxSdkBase.AdViewPosition.Centered); nativeLoader.OnAdLoadedEvent += OnNativeLoaded; nativeLoader.OnAdLoadFailedEvent += OnNativeFailed; } nativeLoader.LoadAd(); } private void OnNativeLoaded(MaxNativeAd ad) { Debug.Log("[MAX] native loaded"); nativeAdView.Bind(ad); // 绑定标题、正文、图标、CTA、MediaView nativeLoader.ShowAd(nativeAdView); // 展示到当前父容器 } private void OnNativeFailed(MaxNativeAd ad, MaxSdkBase.ErrorInfo errorInfo) { Debug.LogWarning($"[MAX] native failed: {errorInfo.Code} {errorInfo.Message}"); } }MaxNativeAdLoader的构造参数里广告位 ID 是必须的,位置参数按插件要求填;OnAdLoadedEvent回调里拿到的MaxNativeAd是素材对象,不要直接当 UI 用。nativeAdView.Bind(ad)这一步把素材塞进预制体字段,ShowAd才真正挂到场景里。errorInfo.Code和Message要打全,No Fill、网络错误、无效广告位 ID 都从这里看。如果示例场景能展示、你的场景不展示,优先对比预制体字段绑定和父容器尺寸,不要先怀疑 SDK。
4.3 加载、展示与回调:主线程和生命周期
Native 广告的加载和展示都要在主线程。Unity 里常见的错误是把回调丢到子线程,或者在OnAdLoaded里做重 UI 操作,表现就是“日志有、画面没有”。我的做法是回调里只改状态,展示统一走一个主线程方法。列表滚动时不要每次OnEnable都重新加载,Native 广告有缓存和频次限制,重复请求会拉低填充率。
private bool pendingShow; private void OnNativeLoaded(MaxNativeAd ad) { Debug.Log("[MAX] native loaded, queue show"); pendingShow = true; cachedAd = ad; } private void Update() { if (!pendingShow || cachedAd == null) return; pendingShow = false; nativeAdView.Bind(cachedAd); nativeLoader.ShowAd(nativeAdView); }这段不是让你把展示硬塞进Update,而是强调回调与展示解耦。实际项目里可以在列表项激活时判断cachedAd是否可用,可用再展示。OnApplicationPause切后台回来时,不要立即重新加载,先看当前广告对象是否还有效;失效了再走Load。广告位 ID 变更、测试模式切换、mediation 网络初始化完成,都可能需要重新加载,但不要在每一帧里判断。
Native 广告的展示位置也要注意:它通常嵌在列表项里,父容器被回收或隐藏时,广告视图要跟着隐藏,不要让它挂在屏幕外继续占资源。列表复用频繁的场景,我一般给每个广告位单独一个宿主节点,复用时先Destroy旧视图再绑定新素材,避免旧 CTA 点击串到新卡片。
5. 常见排查:Native 广告不展示的五个现场
5.1 现象:加载回调成功,屏幕上没有广告视图
现象很典型:Console 里打印native loaded,但列表项还是空白,或者只有背景色。原因通常不是广告没拉到,而是MaxNativeAdView没绑定、父容器高度为 0、预制体字段没挂上,或者ShowAd调了但视图被其他 UI 盖住。解决时先看层级,再看绑定:在场景里临时把广告宿主节点放到最上层,给一个固定高度 300,确认能不能出画面;能出就是布局问题,不能出再查绑定。
// 临时排查:给 Native 宿主一个可见尺寸,确认是不是布局把视图压没了 var rect = nativeAdView.GetComponent<RectTransform>(); rect.sizeDelta = new Vector2(Screen.width, 300); Debug.Log($"[MAX] native view active={nativeAdView.gameObject.activeInHierarchy}, size={rect.rect.size}");sizeDelta只是临时验证,不要留在正式代码里。activeInHierarchy为 false 说明节点被隐藏,rect.rect.size为 0 说明布局没算出来。很多翻车现场是ContentSizeFitter和LayoutGroup互相打架,Native 视图被压成 0 高,日志一切正常,画面就是没有。
5.2 现象:No Fill,错误码提示无填充
现象是OnAdLoadFailed返回 No Fill,或者errorInfo.Message里带 no fill。原因可能是广告位新建还没量、测试设备没配、地区库存低、请求频次太高,或者广告位类型选错。解决顺序是:先确认广告位 ID 是 Native 类型,再在 MAX 后台开测试模式,用测试设备跑一次;还不行就看 Mediation Debugger 里各网络状态,确认 adapter 是否初始化。
# 真机连 Mac 后,用 Xcode Console 过滤 MAX 日志 log stream --predicate 'eventMessage CONTAINS "[MAX]"' --level debuglog stream适合一边操作一边看日志,过滤[MAX]能减少噪音。No Fill 不要连续狂刷,Native 广告有频次控制,越刷越不出。我的习惯是失败后加一个退避,30 秒或 60 秒再重试,列表滚动时也不要在每个条目上同时发请求。
5.3 现象:Xcode 编译报 Undefined symbol
现象是链接阶段报Undefined symbol,符号名里带广告 SDK 或 Objective-C 分类。原因常见三种:CocoaPods 没装、打开的是.xcodeproj而不是.xcworkspace、Other Linker Flags缺-ObjC。解决时先确认 workspace,再跑pod install,最后查 Linker Flags。不要一上来就删 DerivedData,那个只能解决缓存类问题,解决不了缺依赖。
# 确认当前打开的是 workspace,且 Pods 已安装 ls -la Builds/iOS | grep -E "xcworkspace|Podfile.lock" xcodebuild -showBuildSettings -workspace Unity-iPhone.xcworkspace -scheme Unity-iPhone | grep OTHER_LDFLAGSPodfile.lock存在说明 pod install 至少跑过,OTHER_LDFLAGS里要有-ObjC。如果符号来自某个 mediation adapter,还要确认这个 adapter 是否真的被 Podfile 引入。Unity 导出工程有时会保留旧 Podfile,换包后依赖没更新,表现就是主 SDK 在、adapter 不在。
5.4 现象:ATT 弹窗不出现,广告请求状态异常
现象是真机启动后没有 ATT 弹窗,或者弹窗只出现一次后再也不出现。原因可能是Info.plist缺NSUserTrackingUsageDescription、系统里已经拒绝过、测试设备重置后状态没清,或者代码在非主线程请求。解决时先查 plist,再查系统设置里的跟踪权限,最后确认请求时机。ATT 不是广告加载的前置强依赖,但它会影响归因和部分填充,不能放着不管。
<!-- 确认 Info.plist 里有这段,文案按业务改 --> <key>NSUserTrackingUsageDescription</key> <string>用于向你展示更相关的广告,并衡量广告效果。</string>如果 plist 有、系统设置里也重置了,还是不弹,检查是不是在启动瞬间就请求,或者被其他 SDK 先请求了。常见做法是初始化完成后,在第一个合适页面请求一次,拒绝后不要循环弹。模拟器上的 ATT 行为有限,尽量用真机验证。
5.5 现象:上架审核提示隐私清单或 SKAdNetwork 缺失
现象是提审被拒,提示隐私清单缺失或广告归因配置不完整。原因通常是PrivacyInfo.xcprivacy没打进主 Target,或者SKAdNetworkItems只放了一部分。解决时在 Xcode 里看 Target 的 Copy Bundle Resources,确认隐私清单在列表里;再对照 MAX 后台的 SKAdNetwork 列表逐项合并,去重但不漏项。这个环节没有捷径,只能按清单过。
注意:审核问题不要靠猜。把 MAX 后台的 iOS 配置清单打印出来,和 Info.plist、PrivacyInfo.xcprivacy、Podfile.lock 三项逐条对,缺什么补什么。
6. 装完怎么验证:真机日志、MAX Debugger 与三张核对表
验证不要只看“编译通过”。我一般用三张核对表加一条真机日志命令,把环境、配置、回调三层都过一遍。第一张是环境表:Unity 2021.3 LTS、Xcode 14+、iOS 15.0、CocoaPods 1.11+、IL2CPP、ARM64。第二张是 iOS 配置表:NSUserTrackingUsageDescription存在、SKAdNetworkItems按后台补齐、PrivacyInfo.xcprivacy在主 Target、OTHER_LDFLAGS带-ObjC、ENABLE_BITCODE=NO。第三张是广告回调表:初始化成功、Native loaded、视图绑定成功、展示后 CTA 可点、失败回调有错误码。
真机日志用 Xcode Console 或log stream过滤,重点看初始化、加载、展示三段是否连续:
# 真机连 Mac 后过滤 Unity 进程和 MAX 日志 log stream --predicate 'processImagePath CONTAINS "Unity-iPhone" AND eventMessage CONTAINS "[MAX]"' --level debugprocessImagePath过滤 Unity 进程,eventMessage过滤 MAX 日志,--level debug把调试级别带出来。看到init done、native loaded、native view active=true这三类信息,基本说明链路通了。再打开MaxSdk.ShowMediationDebugger()看各网络状态,确认不是只有一个网络在撑。测试模式下的填充和正式环境不同,验证阶段用测试设备,别拿正式广告位反复刷。
| 核对项 | 期望结果 | 不通过时先看 |
|---|---|---|
| Unity iOS 最低版本 | 15.0,与 Podfile 一致 | Player Settings 与 Podfile |
| Linker Flags | 包含-ObjC | Xcode Build Settings |
| ATT 描述 | Info.plist 有键且文案明确 | plutil 与真机设置 |
| SKAdNetwork | 按后台列表补齐 | Info.plist 数组结构 |
| Native 回调 | loaded 后有可见视图 | 预制体绑定与父容器尺寸 |
| 失败日志 | 有 code 和 message | MAX Debugger 与广告位类型 |
这套验证跑完,再把广告位 ID 从测试位换成正式位,重新走一遍加载和展示。别在测试通过后直接提审,正式位的填充、审核状态和测试位不一样,至少留一次真机冷启动验证。我把这次用到的 Info.plist 片段、Podfile 检查项和 Native 回调模板整理在资源包里,按文末入口取,先按第 6 章跑一遍再替换成自己的广告位 ID。从那以后我每次接入新广告位,都先把真机日志和 MAX Debugger 跑一遍再交给测试,不再只看 Unity 编辑器里的日志。希望帮到你。
本文还有配套的精品资源,点击获取