简介:这份资源是面向Unity开发者与手游客户端工程师的华为HMS SDK接入示例工程,聚焦在Unity项目中集成华为账号、游戏服务与推送等能力时遇到的配置与调用问题,适合已具备一定Unity与Android基础、需要快速跑通接入流程的中级开发者参考。压缩包为rar格式,共约2000个文件,整体28.82MB,其中bin、info、class、java、xml等Android构建与配置产物占主体,另有jar、dll、cs脚本与meta、asset等Unity工程文件,以及少量png、log、txt辅助内容,完整保留了示例工程与依赖库的原始结构。资源已有1843人学习下载,说明其在同类接入场景中具备一定参考价值。读者可从中获得可直接对照的工程目录、SDK初始化与登录接口的示例脚本、AndroidManifest权限配置样例以及打包测试所需的工程设置,便于在自己的项目中比对差异、定位版本兼容与权限相关问题,减少从零搭建验证环境的时间。
1. 拿到一个 Unity 接华为 SDK 的 demo,先别急着双击打开
你手上如果有一个 Unity 接华为渠道 SDK 的 demo 包,最忌讳的动作就是直接双击场景点 Play。这类工程十有八九在第一次运行时给你甩一堆NullReferenceException或者ClassNotFoundException,然后你开始怀疑包是坏的。其实问题不在包,在于这类工程天然依赖三样外部东西:Unity 版本、Android 构建环境、以及渠道侧的应用配置。三者缺一,demo 就只是个能打开但不能跑的壳子。
这份资源的价值在于它把「Unity 侧怎么调渠道接口」这条链路完整摆出来了:初始化、登录、支付、回调、生命周期同步,这些环节在纯文档里是散的,在 demo 里是串起来的。适合两类人:一是第一次接华为渠道、想先看一条能跑通的完整调用链的 Unity 开发者;二是接过别的渠道、想对照一下华为这套hmssdk的接口形态和回调时序的老手。下面按「先看懂结构、再动手复现、最后排坑」的顺序拆。
2. 拆开工程看结构:Unity 侧到底封装了什么
2.1 目录分层与三个关键边界
一个典型的 Unity 接渠道 SDK 工程,目录上会呈现明显的三层:Assets下的 C# 业务与桥接层、Plugins/Android下的原生库与清单、以及渠道提供的.aar/.jar依赖。这三层的边界决定了你后面所有排错的方向,先认清楚。
我一般拿到包先做一件事:把Assets里跟渠道相关的脚本单独拎出来看,通常集中在Assets/Scripts/SDK或Assets/Plugins/Android附近。你会看到两类文件——一类是纯 C# 的封装单例(比如HuaweiSDKManager.cs),负责给业务层暴露Login()、Pay()这种干净接口;另一类是AndroidJavaObject/AndroidJavaClass的桥接代码,负责把 C# 调用翻译成 Java 调用。这两类混在一起是常态,但你要能分清哪行是业务、哪行是 JNI。
第二个边界是Plugins/Android下的AndroidManifest.xml。渠道 SDK 要求的权限、meta-data、activity声明、provider配置基本都在这里,而且经常和主工程的清单冲突。第三个边界是.aar依赖本身,它决定了你能调哪些类、方法签名长什么样。demo 里如果带了libs目录,先列一下里面有什么,这比读代码更快建立认知。
# 在工程根目录快速摸清 Android 侧依赖构成 find Assets/Plugins/Android -maxdepth 3 -type f \( -name "*.aar" -o -name "*.jar" -o -name "AndroidManifest.xml" \) | sort这条命令的作用是把原生依赖和清单文件一次性列全,-maxdepth 3防止你掉进深层目录,sort让输出稳定便于对照。跑完你心里应该有一张表:哪些是渠道核心库、哪些是它依赖的第三方库(比如某些 JSON 或网络库)、清单有几个。如果发现同一个库出现两个版本,先记下来,这是后面Duplicate class报错的高发点。
2.2 C# 桥接层怎么读:从单例入口顺藤摸瓜
读桥接代码不要从头读,从业务层调用点倒推。业务里通常是HuaweiSDKManager.Instance.Login()这种写法,那就先找这个单例,看它的Login方法里Call了哪个 Java 方法名。
// 典型的桥接写法,重点看类名、方法名、参数签名三要素 private static readonly AndroidJavaClass SdkBridge = new AndroidJavaClass("com.example.huawei.SdkBridge"); public void Login() { // 第一个参数是 Java 方法名,后面是参数,签名必须和 Java 侧严格一致 SdkBridge.CallStatic("login", CurrentActivity, "default"); } private static AndroidJavaObject CurrentActivity { get { // 拿当前 Activity 是渠道 SDK 的硬需求,很多接口都要传 Context using var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); return unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); } }这段代码的逻辑说明:AndroidJavaClass指向 Java 侧的桥接类,CallStatic调静态方法,第一个字符串是方法名,后面依次是参数。参数说明上,CurrentActivity几乎是所有渠道接口的必备入参,因为 SDK 内部要拿它弹登录页、拉起支付。这里最容易翻车的是方法签名对不上——Java 侧login(Context, String)你写成login(String),运行时直接抛NoSuchMethodError,而且报错信息不会告诉你正确签名是什么。
读的时候顺手做一件事:把每个CallStatic/Call的方法名和参数个数记下来,和后面.aar里反编译出来的方法签名对一遍。对不上的地方就是你要改的桥接点。熟手到这一步基本能判断这个 demo 是「完整可跑」还是「只留了骨架需要自己补 Java 侧」。
2.3 清单合并与权限:先看冲突再看缺失
AndroidManifest.xml的问题分两类:缺权限导致运行时静默失败,以及和主工程清单合并冲突导致构建直接挂。demo 自带的清单通常只覆盖渠道要求的部分,你把它合进自己工程时,minSdkVersion、targetSdkVersion、application节点属性都可能打架。
<!-- 渠道 SDK 常见的清单要求,注意 meta-data 的 name 必须和 SDK 文档一致 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application> <meta-data android:name="com.example.huawei.appid" android:value="你的应用ID" /> <activity android:name="com.example.huawei.LoginActivity" android:configChanges="orientation|keyboardHidden|screenSize" /> </application>逻辑说明:权限缺失不会在编译期报错,只会在运行时某个接口返回失败码,这是最耗时的坑。meta-data的name是 SDK 内部按字符串查找的,写错一个字符就等于没配。activity的configChanges不加,横竖屏切换时登录页会被重建,表现为「登录到一半页面没了」。
参数上重点核对三处:minSdkVersion不能低于渠道要求(低了构建报错,高了老设备装不上)、targetSdkVersion影响权限申请行为、application节点的android:name如果渠道要求自定义 Application 类,必须合并进去而不是覆盖。合并冲突的排查方法是看构建产物里的merged manifest,Unity 导出 Gradle 工程后在build/intermediates/merged_manifests下能找到,冲突项会明确标红。
3. 从零复现:构建、初始化、登录支付三步走
3.1 环境对齐:Unity 版本与 Android 构建链
复现的第一步不是改代码,是把环境对齐到 demo 期望的版本。这类工程对 Unity 版本敏感,尤其是UnityPlayerActivity的包路径和AndroidJavaObject的行为在不同大版本间有差异。我一般先看工程里的ProjectSettings/ProjectVersion.txt,按它标明的版本装,别自作主张升到最新。
Android 构建链要确认三样:JDK 版本、Gradle 版本、以及是否勾选了Custom Main Gradle Template。渠道 SDK 经常要求特定 Gradle 插件版本,Unity 默认模板不一定满足。
# 导出 Gradle 工程后,在 android 目录下确认构建链版本 cd Builds/Android ./gradlew --version # 看 Gradle 与 JVM 版本 grep -r "com.android.tools.build:gradle" build.gradle # 看 AGP 版本逻辑说明:./gradlew --version输出 Gradle 和 JVM 版本,和渠道文档要求对照;grep找 AGP 版本,AGP 与 Gradle 有严格的兼容矩阵,错配的表现是构建到一半报Unsupported class file major version。参数上,如果 demo 没锁版本,我一般按「渠道文档要求的最低版本 + 一档」来配,太新反而容易踩插件不兼容。
提示:导出 Gradle 工程前,先在 Unity 的 Player Settings 里把
Publishing Settings的 keystore 配好,否则每次导出都要重配签名,登录和支付在部分渠道上对签名有校验。
3.2 初始化时序:Application 与 Activity 的先后
渠道 SDK 的初始化对时序极其敏感。常见要求是「在 Application 的onCreate里做一次轻量初始化,在首个 Activity 的onCreate里做完整初始化」。Unity 默认只有一个UnityPlayerActivity,所以桥接层通常要在Awake或Start里主动触发一次初始化调用。
void Start() { // 初始化只做一次,重复调用部分渠道会返回错误码 if (!_inited) { _inited = true; SdkBridge.CallStatic("init", CurrentActivity, BuildConfig.AppId); } } void OnApplicationPause(bool paused) { // 生命周期必须透传给渠道,否则支付回调和登录态续期会断 SdkBridge.CallStatic("onPause"); if (!paused) SdkBridge.CallStatic("onResume"); }逻辑说明:init传Context和AppId,AppId从配置读,别硬编码在脚本里。OnApplicationPause透传是很多人漏掉的一步,漏了之后表现为「切后台再回来,支付回调收不到」或者「登录态失效」。参数上,init的回调通常是异步的,桥接层要注册一个 Java 侧的 listener,C# 侧通过UnitySendMessage或回调接口接收结果,demo 里如果用的是UnitySendMessage,记得接收方法的 GameObject 名和脚本方法名要和 Java 侧字符串完全一致。
初始化失败的排查顺序:先看 logcat 里渠道 SDK 打的错误码,再核对AppId和签名,最后确认网络权限和meta-data。这三步能覆盖八成初始化失败。
3.3 登录与支付:回调链路怎么接住
登录和支付是两条独立的回调链,但结构相似:C# 发起调用 → Java 侧拉起渠道界面 → 用户操作 → 渠道回调 Java → Java 回传 C#。断点通常断在最后一跳。
// Java 侧通过 UnitySendMessage 回调,接收方必须挂在常驻 GameObject 上 public void OnSdkCallback(string json) { var msg = JsonUtility.FromJson<SdkMessage>(json); switch (msg.type) { case "login": // 登录成功拿到的凭证要立刻换游戏侧账号,别只存内存 HandleLogin(msg.code, msg.token); break; case "pay": // 支付回调要校验签名,客户端校验只是第一道,服务端必须再验一次 HandlePayResult(msg.orderId, msg.code); break; } }逻辑说明:UnitySendMessage的第三个参数是字符串,所以 Java 侧一般把结果序列化成 JSON 再传,C# 侧用JsonUtility反序列化。参数上,type区分回调类型,code是渠道返回码,token/orderId是业务凭证。这里的关键约束是接收方法的 GameObject 必须跨场景常驻(DontDestroyOnLoad),否则回调到达时对象已销毁,表现为「支付成功但游戏没发货」。
支付环节还有一条血泪经验:客户端回调成功不等于到账,必须以服务端验签结果为准。demo 里如果只演示了客户端回调,你在接入时一定要补服务端校验,否则就是刷单漏洞。登录同理,token要拿去服务端换正式账号,客户端直接信任返回值是常见误用。
4. 避坑与排查:五条真实翻车记录
4.1 构建期Duplicate class报错
现象:Gradle 构建报Duplicate class com.xxx.xxx found in modules,指向两个不同的.aar或.jar。原因:渠道 SDK 自带的依赖和你工程里已有的第三方库版本重叠,或者 demo 的libs里同时放了完整包和拆分包。解决:用./gradlew app:dependencies打出依赖树,定位重复来源,然后在build.gradle里用exclude group: 'xxx', module: 'yyy'排掉低版本那个。排之前确认渠道 SDK 不依赖被排掉的那个版本,否则运行时会NoClassDefFoundError。
4.2 登录页拉起后白屏或闪退
现象:点登录,渠道登录页闪一下就没了,或者白屏卡住。原因通常是Activity声明缺失或configChanges不全,也可能是targetSdkVersion过高导致渠道页面的权限行为变化。解决:先看 logcat 有没有ActivityNotFoundException,有就补清单声明;没有就检查configChanges是否包含orientation|screenSize|keyboardHidden。还有一种隐蔽情况是主题冲突,渠道页面要求特定theme,被主工程覆盖了,表现为白屏。
4.3 回调收不到:UnitySendMessage静默失败
现象:Java 侧日志显示回调已发出,C# 侧方法没进。原因有三:接收 GameObject 名拼错、对象已被销毁、方法名大小写不一致。UnitySendMessage失败不抛异常,只打一条 warning,很容易被日志淹没。解决:在接收脚本的Awake里打一条带对象名的日志,和 Java 侧传的字符串逐字符对;确认对象挂了DontDestroyOnLoad;方法必须是public void且参数为单个string。
4.4 支付成功但道具没到账
现象:渠道侧显示支付成功,游戏内没发货。原因:客户端回调只代表渠道侧流程走完,服务端验签或发货逻辑失败;或者回调到达时业务对象已销毁。解决:先查服务端订单日志,确认是否收到通知、验签是否通过;再查客户端回调是否进了HandlePayResult。常见误用是把客户端回调当发货依据,正确做法是客户端只做 UI 提示,发货以服务端为准。
4.5 切后台回来登录态丢失
现象:游戏切后台几分钟再回来,需要重新登录。原因:OnApplicationPause没透传给渠道 SDK,或者透传了但没处理onResume里的续期逻辑。解决:确认onPause/onResume都调了,且顺序正确(先 pause 后 resume);再确认渠道 SDK 是否有会话超时配置,超时时间是否过短。部分渠道还要求在onResume里主动调一次checkSession,demo 里如果没写,需要自己补。
5. 进阶:把 demo 变成可维护的接入层
demo 能跑通只是起点,真正上线前要把它改造成可维护的接入层。我的做法是加一层渠道抽象:定义IChannelSDK接口,把Init、Login、Pay、Logout、生命周期透传都抽象出来,华为实现类内部再调hmssdk桥接。这样后面接第二个渠道时,业务层一行不用改。
public interface IChannelSDK { void Init(string appId); void Login(); void Pay(string orderId, int amount); void OnPause(); void OnResume(); } // 华为实现,内部持有桥接对象,业务层只依赖接口 public class HuaweiChannel : IChannelSDK { public void Init(string appId) => SdkBridge.CallStatic("init", CurrentActivity, appId); public void Login() => SdkBridge.CallStatic("login", CurrentActivity, "default"); // 其余方法同理,回调统一走 OnSdkCallback 分发 }参数说明:appId从配置注入而非硬编码,orderId由服务端生成保证唯一,amount用整数分避免浮点误差。这层抽象的成本很低,但收益在第二个渠道接入时立刻体现——你只需要新增一个实现类,回调分发里加一个case。
验证方法上,我习惯做一张最小回归清单:冷启动初始化成功、登录成功、登录取消、支付成功、支付取消、切后台再回来登录态保持、杀进程重进登录态恢复。七项全过才算接入完成,任何一项不过都回到对应章节排查。这张清单我每次接新渠道都强制走一遍,因为渠道 SDK 的坑高度集中在「取消」和「生命周期」这两类边界上,正常流程反而最不容易出问题。
从那以后我每次拿到这类 demo,都先跑一遍这七项回归再动业务代码,省下来的排查时间远比写清单的时间多。希望帮到你。
本文还有配套的精品资源,点击获取