最近做了一个针对 Cocos Creator 2.4.x 的 AdMob 插件测试项目,目标是把原本 Cocos Creator 3.x 官方的 AdMob 接入方案,迁移成 2.4.x 项目可以使用的本地插件。
项目地址结构里,真正用于 2.4.x 的插件是:
packages/admob-24x
而 extensions/GoogleAdMob 更多是从 Cocos 3.x 项目保留下来的参考实现,不能直接照搬。
一、为什么不能直接用 Cocos 3.x 的 AdMob 插件
Cocos Creator 3.x 和 2.4.x 的 Android 构建结构差异很大。
3.x 插件通常依赖:
- 3.x 的 build hook 系统
- 3.x 的 Android 工程目录结构
- 3.x 的 JsbBridge / native bridge 习惯
- 3.x 生成的 Gradle 工程布局
但 Cocos Creator 2.4.x 生成的 Android 工程通常在:
text
build/jsb-default/frameworks/runtime-src/proj.android-studio
它的 AppActivity.java、settings.gradle、app/build.gradle、资源合并逻辑都和 3.x 不一样。
所以这次没有直接复制 3.x 的 BuildTaskAndroid.ts,而是做了一个项目级本地插件:
text
packages/admob-24x
它专门针对 Cocos Creator 2.4.x 的构建结果做补丁。
二、插件整体思路
这个插件不是在运行时动态魔改,而是在 Cocos 构建 Android 后,对生成的 Android Studio 工程做 patch。
核心流程是:
- Cocos Creator 2.4.x 先正常构建 Android。
- 插件监听
before-change-files构建事件。 - 找到生成后的 Android 工程目录。
- 拷贝
libadmob和nativetemplates两个 Android 模块。 - 修改 Gradle 配置。
- 修改
AppActivity.java,接入 AdMob 生命周期。 - 最后在 Android Studio 里同步和打包。
插件入口在:
text
packages/admob-24x/main.js
它做的事情主要包括:
text
copyAndroidModules()
patchRootBuildGradle()
patchGradleWrapper()
patchGradleProperties()
patchSettingsGradle()
patchApplicationGradle()
patchLibraryGradle()
patchAppActivity()
三、Android 模块如何接入
插件会把模板目录里的 Android 模块复制到构建工程:
text
packages/admob-24x/template/android/libadmob
packages/admob-24x/template/android/nativetemplates
复制到目标工程后,大致会变成:
text
build/jsb-default/frameworks/runtime-src/proj.android-studio/libadmob
build/jsb-default/frameworks/runtime-src/proj.android-studio/nativetemplates
然后修改 settings.gradle:
gradle
include ':libadmob'
include ':nativetemplates'
再修改 app 模块依赖:
gradle
implementation project(':libadmob')
这样 AdMob 的 Java 层代码就作为一个 Android Library module 接进了 Cocos 生成的工程。
四、应用 ID 是不是写死的?
测试项目里确实存在 Google 官方测试 ID,例如:
text
ca-app-pub-xxxxxxxxxx/xxxxxxxxxx
ca-app-pub-xxxxxxxxxx/xxxxxxxxxx
ca-app-pub-xxxxxxxxxx/xxxxxxxxxx
这些是测试用的 AdMob App ID 和广告单元 ID。
正式上线时需要替换成你自己的:
- AdMob App ID
- Banner Unit ID
- Interstitial Unit ID
- Rewarded Unit ID
- App Open Unit ID
- Native Unit ID
测试 ID 只能用于开发验证,不能直接上线。
比较合理的做法是:
- 插件层负责 Android 工程接入。
- 游戏业务层负责传入广告单元 ID。
- App ID 在 Android Manifest 或构建配置中写入正式值。
- Demo 场景和测试脚本中保留 Google 官方测试 ID。
这样插件可以复用,具体游戏项目只替换配置。
五、每次构建都会拷贝 admob-24x 吗?
这个插件的设计是:每次 Android 构建时都会重新 patch 当前构建目录。
原因是 Cocos Creator 2.4.x 的 Android 构建产物经常会被重新生成。如果只在第一次拷贝,后续重新构建时可能出现:
libadmob被删除settings.gradle变回原样app/build.gradle丢失依赖AppActivity.java里的初始化代码消失- Gradle 配置被 Cocos 覆盖
所以当前实现选择每次 patch 构建结果。
不过它不是简单粗暴重复插入,而是做了去重处理,例如:
js
appendLineIfMissing(file, "include ':libadmob'");
appendLineIfMissing(file, "include ':nativetemplates'");
AppActivity.java 也会先清理旧的 AdMob 代码,再插入新的代码,避免多次构建后重复出现:
java
import com.cocos.admob.AdServiceHub;
AdServiceHub.instance().init(this);
AdServiceHub.instance().destroy();
这个设计很重要,否则构建几次后 Java 文件就会变脏。
六、AppActivity 生命周期接入
AdMob 原生层需要绑定 Android Activity 生命周期。
插件会修改:
text
app/src/org/cocos2dx/javascript/AppActivity.java
插入 import:
java
import com.cocos.admob.AdServiceHub;
在初始化位置插入:
java
AdServiceHub.instance().init(this);
在销毁位置插入:
java
AdServiceHub.instance().destroy();
这里有一个迁移坑:
不能假设 3.x 的 Activity 结构和 2.4.x 一样。
所以插件里不是盲目用 3.x 的锚点,而是针对 2.4.x 常见结构查找:
java
SDKWrapper.getInstance().init(this);
SDKWrapper.getInstance().onDestroy();
然后分别在它们后面插入 AdMob 初始化和销毁逻辑。
七、Gradle / API 35 兼容问题
这个项目还处理了较新的 Android 构建要求,例如 API 35、AndroidX、Jetifier、Gradle wrapper 等。
插件里定义了这些目标版本:
js
const TARGET_AGP_VERSION = '8.5.2';
const TARGET_GRADLE_VERSION = '8.7';
const TARGET_COMPILE_SDK = '35';
const TARGET_MIN_SDK = '23';
const TARGET_BUILD_TOOLS = '35.0.0';
然后自动修改:
text
build.gradle
gradle-wrapper.properties
gradle.properties
app/build.gradle
libadmob/build.gradle
nativetemplates/build.gradle
同时开启:
properties
android.useAndroidX=true
android.enableJetifier=true
android.suppressUnsupportedCompileSdk=35
这里的技术点是:
Cocos 2.4.x 的 Android 模板比较旧,如果要接新版 Google Mobile Ads SDK,就必须让 Gradle、AGP、compileSdk、AndroidX 这些配置对齐。
否则很容易出现:
- AndroidX 类找不到
- compileSdk 不兼容
- Gradle plugin 版本过低
- native templates 编译失败
- NDK 版本不一致
八、NDK 版本不要乱改
Cocos 2.4.x 项目对 NDK 版本比较敏感。
插件没有强行写死 NDK,而是优先读取生成工程里的:
text
local.properties
从里面解析:
properties
ndk.dir=...
然后把同一个 NDK version 写入相关 Gradle 文件。
这样可以避免:
- app 模块一个 NDK
- libadmob 一个 NDK
- libcocos2dx 又是另一个 NDK
NDK 不一致时,Cocos 原生工程非常容易出现奇怪的编译问题。
九、JS 层怎么调用 AdMob
游戏业务侧不直接调用 Java,也不直接碰 jsb.reflection,而是通过封装好的:
text
assets/script/admob/AdMobSDK.ts
示例:
ts
import AdMobSDK from './admob/AdMobSDK';
AdMobSDK.init();
AdMobSDK.loadAndShowInterstitial('ca-app-pub-xxx/interstitial', {
onLoaded: () => cc.log('Interstitial loaded'),
onFailedToLoad: (error) => cc.log('Interstitial failed', error),
onDismissed: () => cc.log('Interstitial dismissed'),
onFailedToShow: (error) => cc.log('Interstitial show failed', error),
});
激励视频:
ts
AdMobSDK.loadAndShowRewarded('ca-app-pub-xxx/rewarded', {
onEarn: (rewardType, rewardAmount) => {
// 玩家完整看完广告,在这里发奖励
},
onFailedToLoad: (error) => {
cc.log('Rewarded failed', error);
},
onDismissed: () => {
cc.log('Rewarded dismissed');
},
});
这种封装的好处是:
游戏逻辑只关心广告行为,不需要知道 Java 层协议类,比如 LoadRewardedAdREQ、RewardedAdLoadCallbackNTF。
十、支持的广告类型
当前 AdMob 封装覆盖了常见格式:
- Banner
- Interstitial
- Rewarded
- Rewarded Interstitial
- App Open
- Native
对应测试脚本在:
text
script/test/AdmobTestInterstitialAd.ts
script/test/AdmobTestRewarded.ts
script/test/AdmobTestRewardedInterstitialAd.ts
script/test/AdmobTestNative.ts
测试场景里也能看到广告单元 ID 配置。
十一、插屏广告什么时候弹出?
在测试项目里,插屏广告并不是插件自动弹出的。
插件只提供能力,真正什么时候弹,要由游戏业务调用:
ts
AdMobSDK.loadAndShowInterstitial(unitId, callbacks);
或者:
ts
AdMobSDK.loadInterstitial(unitId, callbacks);
AdMobSDK.showInterstitial(unitId);
也就是说,插屏广告的弹出时机应该由游戏控制,例如:
- 关卡结束后
- 返回主页前
- 游戏失败后
- 每 N 局一次
- 冷却时间满足后
- 非新手流程中
这点很重要。
插件不应该擅自决定广告策略,否则很难控制用户体验,也容易违反广告展示节奏要求。
十二、这次迁移踩到的几个坑
1. 不能照搬 Cocos 3.x build hook
3.x 的插件结构和 2.4.x 不同。
最终方案是用 2.4.x 的 packages 本地插件机制,并监听 Builder 事件。
2. 构建目录需要动态识别
不能写死一个路径。插件里会尝试:
text
frameworks/runtime-src/proj.android-studio
proj.android-studio
buildDest
只要找到 settings.gradle,就认为这是 Android 工程根目录。
3. patch 必须幂等
构建可能执行很多次。
如果每次都追加代码,很快就会重复 import、重复 init、重复 dependency。
所以解决方案是:
- settings.gradle 追加前先判断
- dependency 追加前先判断
- AppActivity 先清理旧代码再插入
- Android 模块目录每次重新复制干净版本
4. AppActivity 插入点要基于 2.4.x
3.x 的初始化锚点不一定存在。
2.4.x 常见的是:
java
SDKWrapper.getInstance().init(this);
SDKWrapper.getInstance().onDestroy();
5. 新版 AdMob 需要 AndroidX
Google Mobile Ads SDK 和 native templates 对 AndroidX 更敏感。
所以必须处理:
properties
android.useAndroidX=true
android.enableJetifier=true
6. Cocos 2.4.x 资源合并逻辑要兼容新 AGP
旧模板里可能还有:
gradle
variant.mergeAssets.doLast
新版 AGP 下需要改成:
gradle
variant.mergeAssetsProvider.configure { mergeAssetsTask ->
mergeAssetsTask.doLast {
...
}
}
否则构建阶段可能直接报错。
十三、最终使用流程
实际接入时,推荐流程是:
- 用 Cocos Creator 2.4.x 打开项目。
- 构建 Android。
- 插件自动 patch Android 工程。
- 或手动执行菜单:
text
AdMob 2.4.x > Patch Current Android Build
- 打开:
text
build/jsb-default/frameworks/runtime-src/proj.android-studio
- 用 Android Studio 同步工程。
- 替换正式 AdMob App ID 和广告单元 ID。
- 真机测试广告加载、展示、关闭、奖励回调。
- 最后再接入正式广告策略。
十四、总结
这次 AdMob for Cocos Creator 2.4.x 的核心不是"把 SDK 放进去"这么简单,而是解决三层问题:
第一层是 Android 原生能力:
把 libadmob、nativetemplates、Google Mobile Ads SDK 接进 Android 工程。
第二层是 Cocos 2.4.x 构建适配:
处理 Gradle、AGP、NDK、AndroidX、AppActivity、资源合并等旧模板问题。
第三层是游戏脚本调用:
用 AdMobSDK.ts 封装 JS 到 Java 的调用,让业务层只关心广告加载、展示和回调。
最终得到的方案是一个项目级本地插件:
text
packages/admob-24x
它可以在 Cocos Creator 2.4.x 构建 Android 后自动修补生成工程,让 2.4.x 老项目也能接入较新的 AdMob 能力。
这类迁移最关键的经验是:
不要迷信原来的 3.x 插件结构,要先尊重 2.4.x 生成工程的真实形状。
先手动跑通 Android 工程,再把每一步变成可重复、幂等的 patch,才是比较稳的做法。