news 2026/9/7 16:41:49

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 官方专门为开发者提供了一套测试 ID,Banner 广告位、插屏、激励视频都有对应的测试 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 13,pod 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+ 需要处理 ATT(App 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 (Result<GADInterstitialAd, 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 (Result<GADRewardedAd, 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 原生插件模块没有被正确加载。

排查步骤如下:

  1. 确认前端调用的模块名和原生注册的模块名完全一致,大小写都不能错。
  2. 确认离线 SDK 工程里已经包含插件源码,并且正确参与了编译,而不是只放在文件夹里没加进 Target。
  3. 确认模块清单文件中已经登记该插件。
  4. 确认 HBuilderX 和离线 SDK 版本匹配。

有一次我把 Swift 类的@objc(AdManagerModule)名称改成别的后,忘了同步前端代码,导致模块一直调不起来,当时排查了很久才发现是名称不一致。这种问题最难查,因为编译不报错,运行也不崩溃,就是所有方法回调都没反应。

5.3 上架审核容易被拒的隐私合规设计

接入了 AdMob 后,上架审核和隐私合规是一个绕不开的坎。iOS 14.5 及以上版本对 IDFA 的访问非常严格,AdMob 需要读取 IDFA 来进行广告跟踪。如果用户没有授权 ATT,SDK 会用另一个标识符来代替,广告填充和收益会受影响,但仍能展示广告。

所以我在项目里做了一整套隐私流程:

  1. App 首次启动,先展示自己的用户隐私政策和用户协议。
  2. 用户点击“同意”后,App 读取本地是否保存过同意状态。
  3. 如果从未请求过 ATT,则在合适时机调用系统 ATT 弹窗。
  4. 用户拒绝后不重复弹窗,只在设置页保留手动开启入口。

这个流程不仅仅是应对审核,也是避免一启动就弹窗让用户反感。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,然后选择你的 Team,Xcode 会自动帮你生成对应的描述文件。如果你自己改了 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 导致线上广告位被跑出无效流量。接入广告本来就是细活,细心一点,能帮你省下后面大量的排查时间。

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

MinIO版本回退保姆级实操指南:解决新版功能限制与登录报错

你有没有遇到过这种场景&#xff1a;MinIO上一个版本用得老老实实&#xff0c;升级到新版本之后&#xff0c;控制台登录突然弹出 invalid login access denied&#xff0c;或者之前明明能用的功能&#xff0c;菜单入口直接消失了&#xff1f;我上周就栽在这个上面——测试环境验…

作者头像 李华
网站建设 2026/9/7 16:41:41

Linux服务器大模型部署实战:显存评估、工具选型与性能调优

最近一个月连续帮几个团队搞定了大模型在Linux服务器上的部署&#xff0c;从个人玩票的小项目到要给几十人提供服务的内部平台都碰了一遍。过程中踩了不少坑&#xff0c;也总结出一套相对稳定的落地路径。这篇就把我在Linux服务器上部署大模型的全过程拆开讲清楚&#xff0c;包…

作者头像 李华
网站建设 2026/9/7 16:37:48

美赛D题体育管理建模全攻略:熵权法+TOPSIS+Python实现

2026年美赛D题一出&#xff0c;很多队伍第一反应是“体育运动管理”这个题目太虚了&#xff0c;不像C题给数据、B题给算法那样好上手。但恰恰是这种偏社会科学的题目&#xff0c;反而最考验一个团队把“模糊问题翻译成数学模型”的能力。这篇文章我打算把这题的完整拆解思路、数…

作者头像 李华
网站建设 2026/9/7 16:32:30

C盘爆红怎么办?全面解析清理命令、文件迁移与系统优化技巧

C盘红了&#xff0c;这事估计每个人都遇到过。正写代码呢&#xff0c;突然弹个“磁盘空间不足”&#xff0c;或者开个Photoshop直接卡死&#xff0c;一查C盘还剩几百MB&#xff0c;那心情真的没法形容。我以前也以为C盘爆红只能靠卸载软件、删点视频来治标&#xff0c;直到后来…

作者头像 李华
网站建设 2026/9/7 16:30:21

虚拟偶像MV制作全流程:从角色匹配到音画同步实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华