Unity接入HMS SDK全流程指南:从demo到真机登录避坑
简介在Unity项目中接入华为HMS SDK是移动端开发者的常见需求。由linxinfa整理的这份Demo工程完整演示了从SDK导入、IL2CPP配置到初始化、登录、推送等功能的调用流程适合已掌握Unity基础、想快速接入华为生态的开发者参考。资源包共2000个文件约28.82MB以bin、class、java、xml为主并含jar库、C#脚本与Android工程配置目录结构清晰便于按需定位模块。工程内脚本覆盖了初始化回调、登录接口封装、权限请求等关键代码可移植复用。已有1842人学习下载对照运行能显著缩短接入调试周期规避版本兼容与适配问题。1. Unity 接入 HMS SDK demo为什么我劝你先从 demo 开始如果你搜到这篇手里大概率有一个 Unity 接入 HMS SDK demo 要跑通。别急着把它往正式项目里塞我在这个方向上翻过几次车最典型的场景是包打好了装到测试机上账号登录的按钮怎么点都没反应日志里只有一行HMS Service is not available。后来发现不是代码问题而是从集成方式到签名、再到配置文件的落位每一步都有隐藏的前提。这篇我按自己拆 demo 的顺序写先讲清 HMS SDK 在 Unity 侧到底长什么样再给出一套能直接复现的接入流程最后把真机调试阶段最容易踩的坑列出来。适合刚拿到 demo、准备接账号登录或推送的 Unity 开发者也适合被 Gradle 依赖搞到头疼的熟手回来看边界。2. HMS SDK 这套东西该怎么看能力边界、版本三角与集成选型2.1 HMS SDK 到底拆成哪几块Unity 工程里只会用到其中一部分很多第一次接 HMS SDK 的人会有一个错觉它是一个大而全的 SDK导入 Unity 就什么能力都有了。实际上 HMS SDK 是一组按 Kit 划分的模块账号登录、推送、支付、地图、分析各自独立。Unity 侧拿到的 demo 通常只集成其中两三个 Kit目录结构大致是Assets/Plugins/Android/下面放着厂商提供的一个或几个 aarAssets/Plugins/下面放着 C# 封装层Assets/Scripts/里才是业务调用代码。C# 层不是直接用 Android 的 Java API而是通过封装类把方法暴露给 Unity 脚本。我拿到一个 demo 后习惯先看一眼文件清单而不是直接拖进引擎。下面这个表格是我在模拟项目X里整理出的典型结构不同版本的文件名会有出入但作用基本一致文件 / 目录示例作用说明HmsCorePlugin.aar原生 Android 侧的 SDK 主体包含 Kit 的 Java 实现base_kit.aar/account_kit.aar基础能力与账号能力的拆分模块HmsUnityBridge.jar负责把 Native 层结果封装成 Unity 能识别的消息HMSSDK.cs/HMSAccountService.csC# 层封装脚本直接调用这些类agconnect-services.json开放平台生成的项目配置文件决定 SDK 连到哪个应用第一次接入不需要把每个 aar 都搞明白。你要关注的是两件事一是agconnect-services.json有没有放到正确位置二是 C# 层暴露出来的 API 是不是你当前 Unity 版本能调用的。demo 里的HMSSDK.cs通常已经写了一版初始化流程我的建议是先从Account这个 Kit 开始因为账号登录能直接验证端到端链路是否通。2.2 Unity、Gradle 插件与 HMS SDK 的版本三角约束这是整个接入过程里最容易“玄学”的部分。HMS SDK 本身是 Android 库Unity 打包时会把工程转成 Gradle 工程再编译所以你的 Unity 版本、AGPAndroid Gradle Plugin版本、HMS SDK 版本三者必须形成一个不会冲突的三角关系。以我常用的 2021.3 LTS 为例它自带 Gradle 6.xAGP 4.x而 HMS SDK 的 Unity 插件在对高版本 AGP 支持上存在滞后。只要其中一个版本跳变过大就会出现Could not resolve com.huawei.hms:xxx这种看都看不懂的依赖错误。我一般会按下面几条来约束版本能省掉大量时间Unity 选 LTS 版本不建议用最新预览版接厂商 SDK。如果 demo 里标注了 HMS SDK 版本要求就用它对应的 Unity 版本跑不要自作主张升级。关闭 Unity 的 Auto Gradle Update 选项避免它顺手把 AGP 抬到不兼容的版本。在mainTemplate.gradle里明确指定 AGP 版本而不是用通配符。这个三角约束没什么理论上的优雅解法本质就是一个“官方用哪个版本验证过你就用哪个版本”的思路。demo 里如果附带README或CHANGELOG先看它再看别的。很多人在这一步就放弃了其实是选型错了不是代码错了。2.3 官方自动集成插件 vs 手动导入 demo 文件接入路径有两条一条是安装官方 Unity 插件由工具自动把 SDK 写入工程另一条是手动把 demo 里的 aar、hmssdk.cs、配置文件和 Gradle 片段拷进自己的工程。两条路我都走过结论很直接新手跟 demo 走手动流程不是为了跟官方工具过不去而是手动流程能让你看到每个文件到底落在哪里。自动集成工具确实快但它把过程包成了一个“黑匣子”一旦打包失败你连从哪查起都不知道。手动导入的典型顺序是先把Assets/Plugins/Android整个目录复制到目标工程再检查mainTemplate.gradle里是否声明了厂商仓库和依赖最后把agconnect-services.json放进去。每一步都有对应的失败现象。举个例子mainTemplate.gradle里的仓库声明写错了构建时会直接报找不到 HMS 依赖配置json放错层级运行时又会静默失败登录按钮点了没反应。如果你用的是线上自动集成方案这些问题会被工具隐藏掉一部分排查反而更难。所以本篇文章后面的步骤全部按手动导入 demo 的方式来讲这样你拿到任何一套 demo 都能照猫画虎。3. 搭出第一个能构建的接入 demo工程准备与配置文件落位3.1 在厂商开放平台创建应用拿到一把“钥匙”接 HMS SDK 的第一步不在 Unity 里而在厂商开放平台的控制台上。你需要注册开发者账号创建一个应用记录下应用包名然后在「开发服务」里开通账号 Kit。这个包名必须和 Unity 工程最终打包的包名完全一致否则后面所有接口都会返回鉴权失败。这里有几个关键信息需要在控制台里配置我用参数表列出来配置项填写内容说明应用包名com.yourcompany.yourapp和 UnityPlayerSettings里的包名保持一致应用签名SHA-256 指纹用正式签名的 keystore 计算不是临时签名开通能力Account Kit / Push Kit 等demo 只需要 Account Kit 时别开一堆用不到的回调地址按业务需要填写如果只做 H5 登录跳转才需要纯 App 登录一般不用SHA-256 指纹的获取方式比较固定在命令行导出一个keytool命令从签名文件里读取指纹。如果你暂时没有正式签名可以先创建一个专门给 demo 用的 keystore后续换正式签名时再更新控制台。这里最容易埋雷的是开发阶段用的 debug 签名和上线用的 release 签名指纹不一样经常出现“本地能登录打包给别人就登录失败”的情况。后面第 5 章我会专门讲这个问题的排查。3.2agconnect-services.json到底该放哪放错会怎样从控制台下载下来的agconnect-services.json是一份很小的 JSON 文件里面包含应用 ID、App Secret、各种服务模块的配置信息。它在 Unity 工程里的正确位置是Assets/Plugins/Android/agconnect-services.json。很多第一次接入的人会随手把它放到Assets/根目录结果构建时不报错运行时却拿不到项目信息所有 HMS 接口都像死了一样。这个文件的原理是Unity 打包成 Android 工程时mainTemplate.gradle里有一段解析脚本会把该文件转换成资源文件并打包进 APK。如果文件放错位置脚本找不到它不会中断构建只会跳过解析。所以你在 Unity 编辑器里看不到任何错误提示但真机上 SDK 初始化会失败。验证它是否被正确打进包里的常见做法是用解压工具打开 APK在assets目录下找有没有一个agconnect-services.json或类似名称的文件。这个动作虽然机械但非常管用。如果你的 demo 原本带了一个json但它来自模拟项目X包名和你的工程不一致千万不要直接拿来用。正确流程是在控制台里把你自己的包名和签名指纹配好重新下载一份配置文件替换原来的。这份文件是跟着应用 ID 走的套用别人的配置即使能编译接口签名也会对不上。3.3 Unity 工程侧配置包名、签名、最小系统版本打开Player Settings把Other Settings里的Package Name改成控制台注册的包名。这个改动要和你下载agconnect-services.json之前填写的值一致。接下来是签名配置在Publishing Settings里勾选Custom Main Manifest、Custom Main Gradle Template、Custom Gradle Properties Template然后创建或指定一个 keystore。打包之前建议先确认几个 Android 参数我用一段mainTemplate.gradle的片段示意这些参数在 demo 里通常会默认给好android { compileSdkVersion 31 defaultConfig { minSdkVersion 19 targetSdkVersion 30 multiDexEnabled true } }这段配置里compileSdkVersion 31对应 Android 12是 HMS SDK 比较稳妥的编译基线minSdkVersion 19保证大多数存量安卓机都能跑multiDexEnabled true很重要因为 HMS SDK 的类数量不少不开启可能导致运行时找不到类。需要注意的是targetSdkVersion不要直接拉到 33 甚至更高厂商 SDK 在高版本上如果没适配会出现获取设备信息等接口被系统限制的“翻车”现象。当你不确定用什么版本时优先看 demo 自带的gradle文件那里面的版本组合已经验证过。4. 在 demo 里把账号登录跑通初始化、请求参数与 Unity 回调桥4.1 先搭一个统一入口类来管理 SDK 生命周期账号登录不该散落在各个业务脚本里。我在模拟项目X里习惯写一个HMSBridge类作为 Unity 层和 HMS SDK 之间的唯一入口。它负责初始化、登录、登出和回调分发。这样做的原因是 HMS SDK 的初始化和账号生命周期天然是全局的如果每个页面各自init不仅慢还可能重复注册回调导致事件被多次触发。C# 侧的建议结构如下using UnityEngine; using Vendor.Hms; // 真实工程里替换为厂商 SDK 暴露的命名空间 public class HMSBridge : MonoBehaviour { public static HMSBridge Instance { get; private set; } private bool isInitialized; void Awake() { if (Instance ! null Instance ! this) { Destroy(gameObject); return; } Instance this; DontDestroyOnLoad(gameObject); } public void InitSDK() { if (isInitialized) return; // 检查设备上是否存在 HMS Core 服务 bool available HMSCoreService.IsAvailable(); Debug.Log([HMSBridge] HMS available: available); if (available) { HMSAccountService.Init(); isInitialized true; } } public void Login() { if (!isInitialized) { Debug.LogWarning([HMSBridge] SDK not initialized); return; } HMSAccountService.StartSignIn(); } }这段代码里DontDestroyOnLoad保证场景切换时实例还在HMSCoreService.IsAvailable()是一个前置检查避免在服务不可用的设备上强行调用StartSignIn会拉起原生的账号登录页面。我的建议是Awake里不要立即调用InitSDK因为某些 ROM 上 HMS Core 服务的绑定需要时间可以放在第一次点击登录按钮时再初始化或者在Start协程里稍微延迟一帧再初始化。4.2 拨号键登录与自定义请求参数账号登录有两种常见入口一键登录和自定义账号密码。demo 里通常先实现一键登录因为它的链路最简单。调用前业务方通常要构造一个请求对象把需要申请的权限字段填好。下面这段代码展示登录请求的典型构造方式using Vendor.Hms.Request; using Vendor.Hms.Model; public void RequestLoginByPhone() { var options new AccountLoginOptions { LoginType phoneNumber, // 手机号一键登录 ScopeList new[] { openid, email }, // 需要授权的数据范围 IsFastLogin true, // 是否走静默快速登录 ForceLogin false // 是否强制重新输入账号 }; HMSAccountService.StartSignIn(options, OnLoginResult); } private void OnLoginResult(AccountResult result) { if (result.IsSuccess) { Debug.Log([HMSBridge] login success, code result.AuthCode); } else { Debug.LogError([HMSBridge] login failed, code result.ErrorCode); } }LoginType决定了拉起的是不是一键登录页面ScopeList决定你向用户申请了哪些数据授权只申请登录必要的最小范围能降低用户在授权页取消的概率IsFastLogin为true时如果用户之前授权过可能不会弹页面而直接返回成功ForceLogin为true时则强制走一遍完整登录。这几个参数不是越全越好我在接入第一个 demo 时把ForceLogin设成了true结果每次点击都要求输入密码体验很差。后面改成false用户之前登录过就能快速回到场景。4.3 原生回调到 Unity 的桥接UnitySendMessage 与线程切换HMS SDK 的原生回调发生在 Java 层Unity 侧要收到结果靠的是UnitySendMessage。这条桥的完整链路是Java 层拿到登录结果后调用UnityPlayer.UnitySendMessage(gameObjectName, methodName, jsonString)然后 Unity 场景里对应物体上的方法被触发。把这条链路拆开看有三个最容易出错的点。第一gameObjectName必须和场景里实际存在的物体名完全一致包括大小写。第二接收方法必须是public void且参数是string或object静态方法虽然也能接收但最好不要和实例方法混用。第三回调可能来自非主线程直接操作UnityEngine.Object可能引发诡异错误。下面是一段典型的 Java 侧回调代码示意// 登录成功后把结果回传给 Unity 场景里的 EventSystem UnityPlayer.UnitySendMessage(EventSystem, OnNativeLoginResult, jsonResult.toString());C# 侧对应的接收方法public void OnNativeLoginResult(string message) { // 先切到主线程再处理 UI 更新 UnityMainThreadDispatcher.Instance.Execute(() { var json JsonUtility.FromJsonLoginResultData(message); Debug.Log([HMSBridge] got native result: json.errorCode); }); }这里使用UnityMainThreadDispatcher或者自己在MonoBehaviour.Update里维护一个消息队列都行。我建议不要在OnNativeLoginResult里直接改 UI 文本或刷新动画因为回调线程不一定是主线程。为了省事也可以把消息包成对象塞进队列由Update统一处理这样风险最小。4.4 验证链路是否通看日志、看返回码、看 UI 状态代码写完后先在编辑器里跑是看不出来什么的因为 HMS SDK 依赖真机。把工程构建成 APK 装到支持 HMS Core 的设备上然后用adb logcat抓日志adb logcat -s Unity HMSBridge HMSKit-s后面的标签可以自己加如果你的 demo 在代码里打了[HMSBridge]这样的日志直接搜这个标签就能过滤出海量无关信息。一个连接正常的登录流程日志里至少会出现三条关键输出初始化成功、拉起登录页、回调结果。如果看到初始化成功但点击登录没反应问题多半在签名或配置json如果看到登录拉起但回调丢失问题多半在UnitySendMessage的消息名或 GameObject 是否常驻。返回码也是重要的定位线索。不同的错误码对应着不同的失败位置常见的有返回码含义出现阶段0成功任意阶段907135702网络异常或时区不对初始化 / 登录907135705HMS Core 服务不可用或版本过旧初始化70002001用户取消登录登录页6008证书指纹校验失败鉴权需要注意的是返回码会随 SDK 版本调整我这里只列出最通用的一组。当你看到不认识的代码时先搜索厂商的错误码说明再去看代码。很多开发者在这一步把精力花在改代码上最后发现只是 HMS Core 版本太旧升级一下服务就好了。5. 接入 HMS SDK 的避坑与排查五条血泪经验5.1 坑 1设备上运行时提示 HMS Core 版本过旧接口返回 907135705现象同样的 APK在一台设备上登录正常在另一台设备上点击登录按钮却弹窗要求升级 HMS Core或者接口直接返回 907135705。原因HMS Core 作为系统级服务在不同手机上的版本差异很大。部分设备出厂预装版本较老新版 SDK 调用的接口在旧版本上不存在于是返回版本不兼容错误。解决在初始化之前做能力检查不要直接忽略错误继续调用。我一般会在InitSDK里多写一步如果HMSCoreService.IsAvailable()返回false就跳转到厂商应用市场升级 HMS Core。这个跳转动作需要打开应用市场的详情页你可以在原生层处理也可以把跳转放在 Unity 里引导用户。注意不要试图在代码里静默升级系统服务不是普通应用跨应用安装受限。5.2 坑 2Gradle 构建时提示找不到 HMS SDK 依赖现象Unity 构建到 Gradle 阶段失败日志里有Could not resolve vendor.hms:account:5.x或Failed to resolve的提示。原因HMS SDK 的依赖仓库没有添加到mainTemplate.gradle的repositories里。Unity 默认只配置了 Google 和 Maven Central厂商的私有仓库地址不在其中所以 Gradle 根本找不到依赖。解决在mainTemplate.gradle的allprojects.repositories中添加厂商仓库地址。如果你使用的是国内镜像仓库注意镜像的更新频率某些镜像延迟大刚发布的 SDK 版本会拉不到。我的做法是优先使用官方仓库如果网络访问不到再切换到稳定版本并固定版本号避免在开发过程中依赖悄悄漂移。这个坑表面上是网络问题本质上是配置缺失。5.3 坑 3登录成功但 Unity 场景里收不到回调现象原生登录页面显示了用户输入账号密码并登录成功页面关闭后 Unity 的OnNativeLoginResult却一直没有被调用。原因最常见的是UnitySendMessage的目标物体被销毁了或者目标物体名字对不上。另一个容易被忽略的原因是从原生层回传时使用了中文字符串而 Unity 的JsonUtility对中文转义支持不太友好回调其实到了但解析报错导致看起来像没收到。解决先把游戏物体设为常驻DontDestroyOnLoad可以保证场景切换后它还存在再检查回调方法名是不是公共方法最后用Debug.Log(rawJson)把原始消息打印出来看一眼。如果回调到了但解析失败日志里会有JsonUtility的报错提示。常见做法是回传 JSON 时把中文字段做一次UrlEncode或统一转成 ASCII减少编码上的不确定性。5.4 坑 4混淆或裁剪之后回调失效甚至直接崩溃现象开发模式下一切正常一旦开启 minify 或代码裁剪登录回调就丢了有些设备上直接闪退。原因HMS SDK 的很多类是通过反射被 Unity 调用的混淆工具无法识别这些动态引用会把类名、方法名改掉导致运行时找不到回调入口。SDK 原生的proguard规则如果没有被合并到主工程的混淆配置里这个问题必然出现。解决在主工程的proguard-rules.pro里把 HMS SDK 相关类加入 keep 名单。通常厂商 SDK 会自带一份混淆规则你需要确认它有没有被打包进去。如果没有手动加一行-keep class vendor.hms.** { *; } -keepclassmembers class * { UnitySendMessage methods; }上面这两行规则分别保住 SDK 核心类和所有标记了UnitySendMessage的方法。理解它的逻辑不是为了背配置而是为了排查当你的项目还有直播模块、广告模块时它们的混淆规则也可能和你加的规则冲突。我遇到过一次情况是规则写得太宽把 Unity 的 MainActivity 也 keep 了结果启动速度明显变慢。5.5 坑 5签名指纹不匹配鉴权一直失败现象登录拉起后很快被登出或者返回 6008。开发机没问题换一台新机器打包就失败。原因开放平台上登记的是旧开发机的 debug 签名 SHA-256 指纹而新电脑构建时用了另一个 keystore。HMS SDK 在鉴权时会把应用签名上报给服务端服务端比对不一致直接拒绝。解决每次更换电脑或更换 keystore 时重新计算 SHA-256 并更新到控制台。这里给一个验证指纹的命令keytool -list -v -keystore your-release.keystore -alias your-alias -storepass your-pass把输出里的SHA256:一行复制到开放平台注意去掉冒号和换行。从那以后我每次换电脑都会第一时间跑一遍这条命令并把这个动作写进项目交接清单。签名问题有个特点它不在构建时报错、不在启动时报错只在你登录那一刻才暴露所以定位成本非常高。6. 把 demo 改造成正式项目一个必须养成的验证习惯6.1 从 demo 到正式工程的增量改造顺序demo 跑通只是起点。改造正式工程时我的顺序是先保持 demo 的包名与签名不变把整套 SDK 以最小依赖形式合并进来跑通一次测试包然后逐项替换包名、签名、开放平台配置最后才接入业务登录逻辑。这个顺序反着走很容易把“SDK 问题”和“业务代码问题”混在一起。替换过程中的关键检查项包括PlayerSettings里的包名、开放平台应用包名、agconnect-services.json里的应用 ID、签名文件这三者是否指向同一个应用。任何一项不一致都会让你在调试时产生自我怀疑。你会发现明明代码和 demo 一样但就是登录不了这时候不要反复改代码先把四张身份信息对一遍。6.2 三端日志对照我的固定验证动作我给自己定的一个“强制闭环”是每次接完一个厂商 SDK都要在三处同时开日志。第一处是 Unity 的Debug.Log看业务层日志第二处是adb logcat看 Android 原生层日志第三处是开放平台的接口日志看服务端是否收到请求。三端日志的时间戳对齐后问题能立刻圈定到某一端不用再靠猜。从那以后我每次接这类 SDK 都会强制走一遍“先跑通 demo、再动配置、最后确认三端日志对齐”的流程翻车的概率确实低了很多。希望这篇能帮你少走几趟弯路省下的时间去处理真正有挑战的业务问题。本文还有配套的精品资源点击获取