ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

uni-app 集成 Google AdMob:用 Xcode 制作 iOS 原生广告插件全攻略

uni-app 集成 Google AdMob:用 Xcode 制作 iOS 原生广告插件全攻略 这个项目看着不复杂但真做起来绕的地方不少。前阵子我用 uni-app 做了一个面向海外用户的内容类 App广告变现选的是 AdMob于是就必须把 Google Mobile Ads SDK 接进 iOS 端。可 uni-app 的 js 层根本碰不到 AdMob 这套原生 SDK方案只能是自己写一个 iOS 原生插件再在 Xcode 里完成编译、调试、打包。整个过程下来我对 uni-app 的插件机制、Xcode 工程结构、Google Mobile Ads SDK 的 iOS 接入套路算是彻底捋顺了。这篇文章就把我用 Xcode 制作 iOS 谷歌广告 Google Mobile Ads SDK 插件的完整过程写出来不只讲“怎么做”更多讲“为什么这么做”以及“哪些地方容易踩坑”。如果你是刚接触原生插件的新手或者正准备用 uni-app 接海外广告做变现这篇内容可以直接当参考。1. 需求拆解uni-app 接 Google 广告为什么要自己写原生插件1.1 uni-app 和原生广告 SDK 之间的“语言鸿沟”先理清一个基本概念uni-app 的代码最终会运行在不同平台上在 iOS 上普通页面跑在 WebView / 系统渲染层里而 Google Mobile Ads SDK 是一套纯原生的 Objective-C / Swift 库它负责加载广告、渲染广告、处理点击跳转、展示系统弹窗等一系列行为。这两者之间并不直接相通。你不能在 uni-app 的 vue 页面里写一行new GADBannerView(...)因为 js 引擎里没有这个类。想让两端协作就必须通过 uni-app 提供的原生插件机制在 iOS 端写一个原生模块作为桥接层。这个模块暴露给前端几个方法比如“加载激励视频”“展示插屏”“显示 Banner”前端调用后原生层再调 Google Mobile Ads SDK 完成具体动作。所以很多同学一开始问“有没有 js 插件能直接接 Google Ads”答案是这类插件本质还是原生插件只是有人帮你封装好了。自己动手做一次主要目的不是重复造轮子而是把 AdMob 应用 ID、广告位 ID、初始化时机、回调事件、上架合规这些变量掌握在自己手里不被第三方封装限制。1.2 三个关键技术决策决定了项目走向在动手前我做了几个选型对比这里直接说结论方案适用场景主要局限直接用市场上的 uni-app 广告插件快速出 demo、验证流程不一定持续适配最新 AdMob SDK广告 ID 配置和使用姿势不透明自己写 iOS 原生插件本文主线长期做海外变现、需要稳定控制和调优需要掌握 Xcode、CocoaPods、iOS 原生工程新项目用 uni-app x uts 插件uni-app x 生态的新工程uts 仍处于发展期涉及底层的边界情况需要自己排最终我选择了第二条路线也就是在传统 uni-app 项目里写 iOS 原生插件。原因很实际我的项目是存量 uni-app 工程不想为了接广告把整个项目从 uni-app 重写成 uni-app x同时我希望广告 SDK 的版本自己可控哪天 AdMob 发新版本我只要在 Xcode 工程里改一行 Podfile 就能升级而不是等第三方插件作者更新。需要提醒的是如果你的项目本来就用 uni-app x那应该优先走 uts 原生插件方向它可以直接调用 iOS 原生类桥接层比传统 uni-app 的原生插件更轻。但本文讲的 AdMob SDK 配置逻辑、广告位封装方式、上架合规注意事项对 uni-app x 同样适用底层思路是共通的。2. 开发前环境和工程准备哪些东西必须先确认2.1 一次把工具链准备齐不要中途停下来装环境开始写插件之前我建议你先在 Mac 上确认下面这几项macOS 系统Xcode 版本尽量保持较新HBuilderX用于创建 uni-app 项目、打包自定义调试基座CocoaPods因为 Google Mobile Ads SDK 官方推荐用 Pod 集成一个 Apple 开发者账号个人或公司用于签名真机调试一个 AdMob 账号并且在 AdMob 后台创建好应用拿到 App ID这里最容易忽略的是 AdMob 后台这步。不少人以为代码写好后广告自然能跑结果在真机上调试时一直收不到广告排查半天发现连 App ID 都没拿。AdMob 的 App ID 形如ca-app-pub-3940256099942544~1458002511注意末尾有一个“~”分隔的主号和子号结构。实际上Google 官方专门为开发者提供了一套测试 IDBanner 广告位、插屏、激励视频都有对应的测试 adUnit ID。开发阶段一定要优先使用这些测试 ID而不是直接在真机里请求你自己的正式广告位 ID。原因后面在避坑部分细说这里先记住“测试阶段用测试 ID上架前再换成正式 ID”的原则。2.2 自定义基座和离线打包工程我到底该用哪个这是 uni-app 原生插件开发中新手最容易纠结的问题我分两种情况说清楚。第一种是纯插件逻辑调试。你可以把插件源码放进 uni-app 的原生插件工程里在 HBuilderX 里生成自定义调试基座。这个基座会包含你的原生插件你在前端代码里通过uni.requireNativePlugin(你的模块名)就能调用。这种方式的优点是环境搭建快适合验证基础功能。第二种是深度调试 接第三方 SDK 大量联调。我实际做的时候果断选择了下载官方 iOS 离线打包 SDK用 Xcode 直接打开工程把插件源码放进工程里再加入 Google Mobile Ads SDK 的 Pod 依赖最后在 Xcode 里跑真机进行断点调试。为什么要选第二种因为 Google Mobile Ads SDK 的联调往往会遇到各种奇怪的加载失败、回调不触发、广告展示时机不对等问题。如果在 HBuilderX 的云端自定义基座里调试原生层的日志和断点能力非常受限出了问题你根本看不到底层发生了什么。而直接用 Xcode 打开离线 SDK 工程后你可以在 AdManager 的初始化回调、广告加载成功、广告加载失败这些方法内部打上断点每一步都看得清清楚楚排查效率高非常多。一句话结论验证 API 用自定义基座认真做插件对接用离线打包工程 Xcode 调试。3. AdMob SDK 的接入细节这些配置决定了广告能不能跑起来3.1 用 CocoaPods 引入 Google Mobile Ads SDK在 Xcode 工程里集成 Google Mobile Ads SDK我采用的是 CocoaPods 方式这也是官方推荐方式。先在工程目录下创建或修改Podfile内容大概是这样的platform :ios, 13.0 target 你的Target名称 do use_frameworks! pod Google-Mobile-Ads-SDK, ~ 11.8.0 end注意platform :ios, 13.0这个最低版本。Google Mobile Ads SDK 对 iOS 最低版本的要求会随着版本提升而变化如果你的工程原本支持 iOS 12而 Pod 要求 iOS 13pod install时会直接警告或报错这时按错误提示把最低支持版本调上去即可。不要为了兼容老系统而锁死一个很旧的 AdMob SDK 版本因为 Google 会不断下线老版本导致无法加载广告或后台收不到统计数据。然后执行pod install之后必须用.xcworkspace打开工程而不是.xcodeproj。3.2 Info.plist 里的两项关键配置一项都不能少AdMob SDK 在 iOS 上读取配置主要通过 Info.plist。最核心的配置是GADApplicationIdentifier也就是你在 AdMob 后台拿到的 App ID。如果这个字段不配或配错SDK 会直接拒绝加载广告控制台里通常会出现类似“The Google Mobile Ads SDK was initialized without an application ID”的报错。还有一项是SKAdNetworkItems。这是 Apple 为广告归因提供的框架AdMob 官方会要求开发者把 Google 的 SKAdNetwork ID 列表加入到 Info.plist。不去配置它广告不至于完全无法展示但广告主的归因数据会不准确长期来看影响填充和收益。Google 官方文档里有一份现成的 plist 片段复制进去就行。另外还有一个容易被忽视的点如果你的 App 要上架 App Store并且会调用广告 SDK那么 App 隐私相关配置也需要同步检查。新版 AdMob 在 iOS 14.5 需要处理 ATTApp Tracking Transparency也就是“是否允许 App 跟踪你”的弹窗。Info.plist 里需要加NSUserTrackingUsageDescription并在合适的时机向用户请求授权。这里有个很实际的经验不要一启动 App 就弹 ATT最好在用户已经看完隐私政策并且明确同意后再弹否则审核和用户体验都会出问题。3.3 用 Swift 封装广告管理器方便前端统一调用我不建议把广告逻辑全写在一个 DCUniModule 类里那样类会变得很臃肿。推荐的做法是单独写一个AdManager原生类专门负责和 Google Mobile Ads SDK 打交道然后 DCUniModule 只做方法转发和事件分发。这里我按 Banner、插屏、激励视频三种广告位分别演示关键调用。先做原生广告管理器的初始化import GoogleMobileAds class AdManager: NSObject { static let shared AdManager() private override init() {} func start() { GADMobileAds.sharedInstance().start { status in // status 里可以看到每个适配器的初始化情况 } } }然后是 Banner 的加载与展示。Banner 本质上是一个原生 UIView所以难点在于它怎么和 UniApp 的 WebView 页面共存。我在实际项目里采用的方案是把 Banner 加在原生根控制器视图的底部并通过事件通知前端“广告高度是多少”让前端页面预留出底部空间。代码大致是这样func showBanner(with adUnitID: String, rootVC: UIViewController) { let banner GADBannerView(adSize: GADAdSizeBanner) banner.adUnitID adUnitID banner.rootViewController rootVC banner.delegate self banner.frame CGRect(x: 0, y: rootVC.view.bounds.height - 50, width: rootVC.view.bounds.width, height: 50) rootVC.view.addSubview(banner) banner.load(GADRequest()) }插屏广告更简单它是一个覆盖在当前界面之上的全屏弹窗func loadInterstitial(adUnitID: String, completion: escaping (ResultGADInterstitialAd, Error) - Void) { GADInterstitialAd.load(withAdUnitID: adUnitID, request: GADRequest()) { ad, error in if let error error { completion(.failure(error)) return } completion(.success(ad)) } }激励视频广告的加载和展示则长这样func loadRewardedAd(adUnitID: String, completion: escaping (ResultGADRewardedAd, Error) - Void) { GADRewardedAd.load(withAdUnitID: adUnitID, request: GADRequest()) { ad, error in if let error error { completion(.failure(error)) return } completion(.success(ad)) } }这些 API 在 Google 的新版本 SDK 中可能会调整比如部分回调方法名有变化但整体结构是稳定的。你直接对照当前安装版本的 SDK 头文件或官方文档微调即可。3.4 DCUniModule 桥接层把原生能力暴露给 uni-app 前端原生广告类写好后就需要通过 uni-app 的 Module 把它暴露给前端。在 iOS 原生插件工程里一般新建一个类继承 DCUniModule例如objc(AdManagerModule) class AdManagerModule: DCUniModule { objc func loadRewardedVideo(_ adUnitId: String, callback: escaping DCUniModuleCallback) { AdManager.shared.loadRewardedAd(adUnitID: adUnitId) { result in switch result { case .success: callback([code: 0, message: load success]) case .failure(let err): callback([code: -1, message: err.localizedDescription]) } } } }这段代码里需要留意一个关键点DCUniModule 的类名、导出宏、事件发送 API 会随着 HBuilderX 的离线 SDK 版本不同而变化。不同教程里写的注册方式也可能不一样所以在你自己项目里遇到“模块加载不了”“方法找不到”时先检查当前版本的离线 SDK 头文件以官方文档和实际类定义为准这是最稳的做法不要照抄网上的旧代码。在前端页面里调用方式就很直白了const adModule uni.requireNativePlugin(AdManagerModule) adModule.loadRewardedVideo(ca-app-pub-3940256099942544/1712485313, (res) { console.log(load result, res) })前端拿到原生模块返回的加载结果后再决定展示或提示用户“广告暂未准备好”。4. 从插件源码到真机跑通整个流程分几步4.1 当前前端工程的页面和调用时机设计原生的广告能力接好后前端不能乱调。我的建议是封装一个公共的广告 Service比如adService.js页面不需要知道底层是原生还是 js只调用loadRewardedVideo()、showRewardedVideo()、showBanner()这样的方法即可。一个比较典型的激励视频交互流程是用户在页面里点击“看视频领奖励”按钮前端先调用原生加载激励视频。如果广告已经加载好了就立刻展示用户看完视频后在原生回调里触发“发放奖励”事件如果广告还没加载好就显示“广告准备中”同时后台静默开始预加载下一条。这里有一个我踩过的坑激励视频广告是“一次性”的展示完之后这个对象就不能再用于第二次展示。一定要在广告展示完成或关闭后马上进行下一次预加载否则用户连续点击第二次时会因为没有可用广告而体验很差。4.2 HBuilderX 离线打包中常踩的坑版本一致性和模块注册离线 SDK 开发有一个最容易被忽略的匹配规则HBuilderX 的版本必须和 iOS 离线 SDK 的版本对应得上。如果你 HBuilderX 是 4.x却拿了一个 3.x 的离线 SDK 来改原生模块的接口定义对不上编译能过但运行时大概率报“module not found”或某个方法不存在。这类问题往往让人先怀疑代码最后才发现是版本不匹配。所以正确步骤是先在 HBuilderX 里查看你当前使用的版本号然后去 DCloud 下载对应版本的 iOS 离线打包 SDK。拿到一个新工程后不要急着改业务代码先用 Xcode 直接编译运行一次官方 Demo确认环境可用再开始植入自己的插件代码。这一步能帮你隔离大量“环境问题”和“代码问题”。4.3 插件逻辑测试之后如何打成正式包验证自定义基座调试通过后还需要把插件打包进正式安装包验证在 release 模式下 AdMob 广告是否正常。如果使用的是离线打包 SDK直接在 Xcode 里选择 Archive 导出安装包即可。注意 bundle identifier、证书签名要配置正确广告 ID 换成正式 ID同时做一遍完整的 AdMob 后台配置。很多开发者只在开发环境里测试广告结果正式包发布后才发现收不到任何广告。常见原因不外乎三种正式广告位 ID 没有创建或没有启用AdMob 后台 App 状态没配置成“上线”或者金融、健康等特殊类别内容被 AdMob 限制。所以正式包上线前一定要用真实环境完整测试一次广告加载。4.4 Bambo 广告位置 UI 布局的取舍推荐用全屏广告位稳定落地如果你想在 uni-app 页面内部嵌入 Banner会面对一个 WebView 与原生视图无法同层渲染的老问题。常规 Banner 是原生 View不能直接放在普通 vue 页面中间的某个div里。我试过几种方案最稳定的是把 Banner 做成页面底部或顶部的原生覆盖条再通过事件把高度同步给前端页面做避让。如果只是临时验证也可以把 Banner 放在 App 的根视图底部但这样做会遮挡内容。在早期版本中有人会用subNVue或 nvue 页面来承载原生 Banner但这会引入更多跨端判断Banner 在 Android 和 iOS 上的表现还不太一样。所以就我个人的经验来说如果你做的是一个工具类或内容类 App首次接入 AdMob 时优先做激励视频和插屏会更稳妥这两个广告位都是全屏覆盖展示不涉及布局层嵌套问题接入成本低、收益模式也更容易跑通。等全屏广告稳定了再回头研究 Banner 和原生视图共存的问题。5. 常见问题与排查技巧实录5.1 广告总是加载失败先按这个顺序排查我在联调阶段遇到最多的问题就是广告加载失败。这里整理了一个排查顺序基本能解决九成情况现象排查点控制台直接报缺少 Application ID检查 Info.plist 里GADApplicationIdentifier是否配置值是否和 AdMob 后台一致加载错误码为 0 或请求无效检查 adUnit ID 是否用了正式 ID、是否格式错误开发阶段优先换测试 ID请求一直不返回断网或 SDK 初始化未完成确认GADMobileAds.sharedInstance().start()已调用加载成功但展示黑屏/无反应检查展示时传入的 rootViewController 是否为当前可见控制器不要传一个已经被 dismiss 的控制器激励视频只能看一次展示完成回调里没有触发下一次预加载重新loadRewardedAd即可记住控制台的日志是排查的第一现场。Xcode 工程里如果开启了 AdMob 的详细日志SDK 会打印很多有用的调试信息别忽略它们。5.2 原生插件在 uni-app 里报“module not found”怎么办这个问题几乎每个写原生插件的人都会遇到。它通常不是 Google 广告的问题而是 uni-app 原生插件模块没有被正确加载。排查步骤如下确认前端调用的模块名和原生注册的模块名完全一致大小写都不能错。确认离线 SDK 工程里已经包含插件源码并且正确参与了编译而不是只放在文件夹里没加进 Target。确认模块清单文件中已经登记该插件。确认 HBuilderX 和离线 SDK 版本匹配。有一次我把 Swift 类的objc(AdManagerModule)名称改成别的后忘了同步前端代码导致模块一直调不起来当时排查了很久才发现是名称不一致。这种问题最难查因为编译不报错运行也不崩溃就是所有方法回调都没反应。5.3 上架审核容易被拒的隐私合规设计接入了 AdMob 后上架审核和隐私合规是一个绕不开的坎。iOS 14.5 及以上版本对 IDFA 的访问非常严格AdMob 需要读取 IDFA 来进行广告跟踪。如果用户没有授权 ATTSDK 会用另一个标识符来代替广告填充和收益会受影响但仍能展示广告。所以我在项目里做了一整套隐私流程App 首次启动先展示自己的用户隐私政策和用户协议。用户点击“同意”后App 读取本地是否保存过同意状态。如果从未请求过 ATT则在合适时机调用系统 ATT 弹窗。用户拒绝后不重复弹窗只在设置页保留手动开启入口。这个流程不仅仅是应对审核也是避免一启动就弹窗让用户反感。AdMob 在欧盟地区对用户同意还有更严格的 UMP 要求如果你的 App 面向全球用户建议直接用 Google 的 UMP SDK 来做同意管理它能和 AdMob 无缝衔接。5.4 处理“用户不同意隐私政策就退出 App”的需求我在需求评审时收到过这样一个要求用户如果不同意隐私政策和用户协议就直接退出 App不给任何使用机会。这个做法在很多涉及用户隐私要求的 App 里很常见。实际实现逻辑不难核心是在同意状态为 false 时不初始化广告 SDK、不请求 ATT、不收集任何个人信息然后调用plus.runtime.quit()这类 API 退出 App。要注意的是退出前最好给出一个友好的提示页面而不是直接闪退。另外如果用户同意过隐私政策但后来在系统设置里关闭了广告跟踪权限这并不等于用户拒绝 App 的隐私政策App 可以继续运行只是不读取 IDFA。这两件事不能混为一谈。5.5 Xcode 工程运行时还容易出签名、描述文件和 target 不一致接 AdMob 插件的过程中有一个很容易让人心态崩溃的问题并不在广告本身而在于 Xcode 工程的环境配置。尤其是第一次用离线 SDK 工程时替换 bundle id 后签名和描述文件经常对不上导致真机安装失败。建议直接使用自动签名管理。在 Xcode 的 Signing Capabilities 里勾选 Automatically manage signing然后选择你的 TeamXcode 会自动帮你生成对应的描述文件。如果你自己改了 bundle id记得先在开发者后台添加对应 App ID否则自动签名也帮不了你。还有一种情况是工程里同时存在多个 target广告代码加到了错误的 target导致你改了半天代码但实际运行的根本不是这个 target。我在联调阶段就经历过一次改了插件代码后发现没有生效最后才注意到 Xcode 顶部选中的 target 和我在 Podfile 里配置的 target 不是同一个。这个细节平时容易忽略但遇到“改了没效果”的问题时优先检查它。6. 我踩过坑后的几点真实感受做完整个 Google Mobile Ads SDK 插件的开发和接入我最深的体会是uni-app 做跨端业务确实方便但一旦涉及原生 SDK还是要老老实实回到原生工具链里去解决问题。Xcode、CocoaPods、Swift 这些技能平时可能用不上可在广告变现这条路上它们是绕不开的必修课。对于时间紧、预算有限的团队我建议第一步不要追求把 Banner、插屏、激励视频、开屏广告一次性全接完。先挑最核心的一种广告位打通闭环把原生插件模板跑通确认自定义基座、离线打包、上架合规这些流程都没问题再按同样的套路复制其他广告位。这样可以少走很多弯路也不会因为一开始就想做太全而陷入并发调试的泥潭。最后再分享一个我个人的小习惯所有广告相关的 ID 都会单独放在一个配置文件里区分测试环境和生产环境并且把测试广告 ID 和正式广告 ID 用常量隔离开来。这样哪怕团队接手的人换了也不会因为改错 ID 导致线上广告位被跑出无效流量。接入广告本来就是细活细心一点能帮你省下后面大量的排查时间。
返回列表