☰
Unity接入华为SDK实战:登录支付最小闭环与避坑指南
2026/9/29 16:11:19 网站建设 项目流程

简介:本资源是面向Unity开发者、尤其是需要在华为设备上发布游戏的移动端工程师的华为HMS SDK接入示例工程,围绕账号登录、推送、游戏服务等常见能力给出可运行的集成参考。压缩包共约2000个文件,以bin、info、class、meta、java、xml、png、jar、cs、dll等为主,涵盖Android原生库、Unity脚本、资源清单与配置说明,整体约28.82MB,结构接近真实工程目录,便于对照排查。资源中保留了HuaweiSdkDemo示例代码与初始化、登录等接口调用片段,可帮助读者理解IL2CPP后端下的接入流程、权限配置与打包测试要点,并作为版本兼容与报错排查的参照。目前已有1842人学习下载,适合具备一定Unity与Android基础、希望快速跑通华为渠道接入的开发者参考使用。

1. Unity接入华为SDK demo:从零跑通登录与支付的最小闭环

很多做 Unity 手游的团队第一次接华为 SDK,都会卡在同一个地方:Unity 编辑器里跑得好好的,一打安卓包就黑屏、闪退,或者登录按钮点下去毫无反应。这不是代码写错了,而是 Unity 和华为 SDK 之间隔着一层安卓原生桥接,编辑器环境根本模拟不了。这篇笔记要讲的就是怎么从零搭一个能跑通的 Unity 接入华为 SDK demo,把登录、支付这两个最核心的能力先跑起来,再谈其他。适合已经会写 C#、但对安卓原生交互不熟、又必须把华为渠道包发出去的 Unity 开发者。我会按真实接入顺序走一遍:环境准备、AAR 导入、桥接层写法、回调处理、打包验证,最后把几个最容易翻车的地方单独拎出来讲。全程不依赖任何现成的商业插件,纯手工接,这样你才知道每一步到底在干什么。

2. 接入前的环境与工程结构:为什么不能直接在编辑器里测

2.1 Unity 与华为 SDK 的版本匹配逻辑

华为 SDK 对 Unity 版本和安卓构建管线有明确要求,但官方文档往往只给一个范围,实际踩坑都出在细节上。我一般会锁定 Unity 2021 LTS 或 2022 LTS,因为这两个版本对 Gradle 和 AndroidX 的支持最稳,再新的版本有时候反而会因为 AGP 升级导致 AAR 冲突。华为 SDK 这边,AppGallery Connect 的 SDK 包通常分两部分:基础能力包(agconnect-core)和具体服务包(如 auth、iap)。基础包必须和具体服务包版本对齐,否则运行时会报类找不到。

构建管线必须切到 IL2CPP + ARM64,这点没有商量余地。华为从某代机型开始就只收 ARM64 的包,用 Mono 打出来的包连安装都过不了。Player Settings 里 Minimum API Level 建议设到 24,Target API Level 设到 33 或更高,具体看华为后台当时的要求。Scripting Backend 选 IL2CPP 之后,打包时间会明显变长,这是正常的,别以为是卡死了。

提示:每次改完 Player Settings 里的架构或 API Level,最好删掉 Library 目录重新导入一次,否则 Gradle 缓存可能带着旧配置一起打包。

2.2 工程目录该怎么摆:Plugins/Android 下的文件组织

Unity 接入任何安卓 SDK,核心就是把 AAR 和 AndroidManifest 放到正确的位置。标准做法是在 Assets 下建 Plugins/Android 目录,把华为给的 .aar 文件直接丢进去。如果有多个 AAR,注意它们的依赖关系,比如 auth 的 AAR 依赖 core 的 AAR,两个都要放,缺一个就会在打包时报 NoClassDefFoundError。

AndroidManifest.xml 也要放在 Plugins/Android 下。华为 SDK 需要在 Manifest 里声明一些权限和组件,比如网络权限、读取设备信息权限,以及 AppGallery Connect 的 provider。这个文件不能直接覆盖 Unity 自动生成的那个,而是要用 Unity 的 Manifest 合并机制,只写增量部分。我一般会保留 Unity 默认生成的 Manifest 作为基础,然后手动把华为要求的节点插进去。

Assets/ Plugins/ Android/ agconnect-core-1.9.0.300.aar huawei-auth-1.9.0.300.aar huawei-iap-1.9.0.300.aar AndroidManifest.xml Scripts/ HuaweiSDK/ HuaweiBridge.cs HuaweiCallbackHandler.cs

这个结构看起来简单,但实际项目里经常有人把 AAR 放到 Assets 根目录下,Unity 也能识别,但打包时容易漏掉依赖。统一放 Plugins/Android 是最稳的。

2.3 为什么编辑器模式必须做条件编译

Unity 编辑器跑在 Windows 或 Mac 上,根本没有安卓运行时,所有华为 SDK 的 Java 类都不存在。如果你直接在 C# 里调用 AndroidJavaClass,编辑器里会直接抛异常,整个游戏都跑不起来。所以桥接层必须用 UNITY_ANDROID && !UNITY_EDITOR 这样的宏把真机代码包起来,编辑器里走一套空实现或者模拟返回。

public class HuaweiBridge { public static void Init() { #if UNITY_ANDROID && !UNITY_EDITOR using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) using (var huaweiApi = new AndroidJavaClass("com.huawei.hms.api.HuaweiApiAvailability")) { int status = huaweiApi.CallStatic<int>("isHuaweiMobileServicesAvailable", activity); if (status != 0) { Debug.LogError("HMS Core not available, status: " + status); return; } } Debug.Log("Huawei SDK init success"); #else Debug.Log("Editor mode, skip Huawei SDK init"); #endif } }

这段代码的逻辑是:先拿到 Unity 当前的 Activity,然后通过华为的 HuaweiApiAvailability 检查设备上有没有安装 HMS Core。status 为 0 表示可用,非 0 就是各种异常情况,比如没装、版本太低、需要升级。编辑器模式下直接打日志跳过,保证游戏逻辑不受影响。参数方面,currentActivity 是 Unity 自动维护的,不需要自己创建;HuaweiApiAvailability 的类名和包名必须和 AAR 里的完全一致,写错一个字母就会在真机上崩。

3. 登录与支付的最小实现:从 Java 桥接到 C# 回调

3.1 用 AndroidJavaProxy 处理登录回调

华为的登录接口是异步的,调用之后结果通过回调返回。Unity 这边要用 AndroidJavaProxy 来模拟一个 Java 接口的实现。华为的 Auth 服务里,登录回调接口通常是 com.huawei.hms.support.hwid.result.AuthHuaweiId 相关的 listener。你需要先查清楚当前 SDK 版本里回调接口的完整类名和方法签名,然后写一个对应的 C# 代理类。

public class HuaweiLoginCallback : AndroidJavaProxy { public Action<string> OnSuccess; public Action<int, string> OnFailure; public HuaweiLoginCallback() : base("com.huawei.hms.support.hwid.service.HuaweiIdAuthService$AuthResultListener") { } void onSuccess(AndroidJavaObject authHuaweiId) { string openId = authHuaweiId.Call<string>("getOpenId"); string displayName = authHuaweiId.Call<string>("getDisplayName"); OnSuccess?.Invoke(openId + "|" + displayName); } void onFailure(int errorCode, string errorMsg) { OnFailure?.Invoke(errorCode, errorMsg); } }

这个代理类的关键是 base 构造函数里的接口全名,必须和 AAR 里定义的完全一致。onSuccess 和 onFailure 的方法名也要对得上,Java 那边怎么写的,C# 这边就得怎么命名,大小写都不能错。拿到 authHuaweiId 之后,通过 Call 方法反射调用 getOpenId 和 getDisplayName,这两个是华为账号的唯一标识和昵称,用来做游戏内的账号绑定。

调用登录的代码大概长这样:

public static void Login(Action<string> onSuccess, Action<int, string> onFailure) { #if UNITY_ANDROID && !UNITY_EDITOR using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) using (var authManager = new AndroidJavaClass("com.huawei.hms.support.hwid.HuaweiIdAuthManager")) { var callback = new HuaweiLoginCallback { OnSuccess = onSuccess, OnFailure = onFailure }; var paramsBuilder = new AndroidJavaObject("com.huawei.hms.support.hwid.request.HuaweiIdAuthParamsHelper"); var authParams = paramsBuilder.Call<AndroidJavaObject>("setIdToken") .Call<AndroidJavaObject>("setProfile") .Call<AndroidJavaObject>("createParams"); var service = authManager.CallStatic<AndroidJavaObject>("getService", activity, authParams); service.Call<AndroidJavaObject>("signIn", callback); } #else onSuccess?.Invoke("editor_test_openid|EditorUser"); #endif }

这里先构建 HuaweiIdAuthParamsHelper,设置需要 idToken 和 profile 信息,然后 createParams 生成参数对象。再通过 HuaweiIdAuthManager.getService 拿到服务实例,最后调 signIn 并传入回调。编辑器模式下直接返回一个假的 openId,方便你在编辑器里调试后续逻辑。

3.2 支付接口的调用顺序与参数含义

华为支付(IAP)的流程比登录多几步:先查商品信息,再发起购买,最后处理购买结果。商品信息需要在 AppGallery Connect 后台提前配置好,拿到 productId 之后才能在代码里查。查询商品用 com.huawei.hms.iap.IapClient 的 getProductInfo 方法,传入 productId 列表和价格类型。

public static void QueryProducts(string[] productIds, Action<string> onResult) { #if UNITY_ANDROID && !UNITY_EDITOR using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) using (var iapClient = new AndroidJavaClass("com.huawei.hms.iap.Iap")) { var client = iapClient.CallStatic<AndroidJavaObject>("getIapClient", activity); var request = new AndroidJavaObject("com.huawei.hms.iap.entity.ProductInfoReq"); request.Set("priceType", 0); // 0 表示消耗型商品 request.Set("productIds", productIds); var result = client.Call<AndroidJavaObject>("getProductInfo", request); onResult?.Invoke(result.Call<string>("getProductInfoList")); } #else onResult?.Invoke("editor_mock_product_list"); #endif }

priceType 这个参数容易搞混:0 是消耗型(比如金币、道具),1 是非消耗型(比如去广告),2 是订阅型。设错了后台对不上,查出来的商品列表就是空的。productIds 是一个字符串数组,对应后台配置的商品 ID,大小写敏感。

发起购买用 buy 方法,传入 ProductInfo 对象和回调。购买回调里会返回一个 PurchaseResultInfo,里面包含订单状态和购买令牌。订单状态分几种:已购买、已取消、已退款等。拿到购买令牌之后,还要把令牌发给你的游戏服务器,由服务器去华为的订单校验接口验证,验证通过才发货。这一步绝对不能省,否则就是典型的“客户端自校验”漏洞,很容易被刷单。

3.3 回调线程与 Unity 主线程的交互

华为 SDK 的回调不一定在 Unity 主线程上执行。如果你在回调里直接操作 GameObject 或调用 Unity API,可能会报 “can only be called from the main thread” 的错误。稳妥的做法是在回调里只做数据解析,然后把结果塞到一个线程安全的队列里,在 Update 里取出来再处理。

private static readonly ConcurrentQueue<Action> _mainThreadQueue = new ConcurrentQueue<Action>(); public static void Enqueue(Action action) { _mainThreadQueue.Enqueue(action); } void Update() { while (_mainThreadQueue.TryDequeue(out var action)) { action?.Invoke(); } }

在华为回调的 onSuccess 里,不要直接更新 UI,而是 Enqueue 一个 lambda,把后续逻辑放进去。这样不管回调在哪个线程,最终都在主线程执行。这个模式在接入任何安卓 SDK 时都通用,不只是华为。

4. 打包与真机验证:从 Gradle 报错到登录成功

4.1 自定义 Gradle 模板解决依赖冲突

Unity 默认的 Gradle 模板有时候会和华为 SDK 的依赖打架,典型报错是 “Duplicate class” 或者 “Program type already present”。这时候需要开启 Custom Main Gradle Template 和 Custom Launcher Gradle Template,在 mainTemplate.gradle 里手动加华为的 Maven 仓库和依赖。

repositories { maven { url 'https://developer.huawei.com/repo/' } } dependencies { implementation 'com.huawei.hms:base:6.9.0.301' implementation 'com.huawei.hms:auth:6.9.0.301' implementation 'com.huawei.hms:iap:6.9.0.301' }

版本号要和 AAR 文件对应,不能随便写。如果 AAR 是 1.9.0.300,Gradle 里也写 1.9.0.300。仓库地址是华为的官方 Maven,必须加,否则 Gradle 找不到这些包。加完之后如果还报冲突,用./gradlew :launcher: dependencies看依赖树,找到冲突的包用 exclude 排除。

4.2 真机日志抓取与常见错误码

真机调试最怕的就是闪退没日志。安卓这边可以用 adb logcat 抓,过滤 Unity 和华为的 tag:

adb logcat -s Unity:V Huawei:V HMS:V AndroidRuntime:E

AndroidRuntime 的 E 级别日志会打出崩溃堆栈,这是定位闪退的第一手资料。华为 SDK 常见的错误码有:907135000 表示参数错误,907135001 表示未登录,907135002 表示网络异常,6003 表示用户取消。这些错误码在华为的 API 文档里都有,但文档往往只给一个列表,实际排查时要结合 logcat 里的上下文看。

注意:如果 logcat 里看到 “HMS Core not available”,先确认测试机是不是华为或荣耀设备,非华为设备需要单独安装 HMS Core APK 才能跑。

4.3 用华为后台的沙盒环境做支付测试

支付测试不要用真实账号直接付钱,华为提供了沙盒环境。在 AppGallery Connect 后台把测试账号加到沙盒名单里,然后用这个账号登录游戏,发起的支付不会真实扣款,但流程和真实支付完全一样。沙盒环境的订单校验接口也是独立的,服务器那边要区分对待。

测试的时候重点看三个地方:一是 buy 接口有没有正常返回 PurchaseResultInfo;二是订单状态是不是 0(已购买);三是服务器校验接口有没有返回成功。三个都过了,才算支付链路通了。任何一个环节断了,先看 logcat,再看后台的订单记录,基本能定位到问题。

5. 避坑与排查:接入华为 SDK 时最容易翻车的五件事

5.1 现象:编辑器里正常,真机一启动就闪退

原因:最常见的是 AAR 版本和 Gradle 依赖版本不一致,或者 AndroidManifest 里少声明了华为的 provider。另一个高频原因是 Scripting Backend 没切到 IL2CPP,或者架构没选 ARM64。

解决:先看 logcat 的 AndroidRuntime 堆栈,如果是 ClassNotFoundException,就是 AAR 没打进去或者被裁剪了。检查 Plugins/Android 下的 AAR 是否完整,Gradle 依赖是否和 AAR 版本对齐。如果是 Manifest 问题,对比华为文档里的 Manifest 示例,逐项检查权限和组件声明。

5.2 现象:登录回调不触发,点按钮没反应

原因:AndroidJavaProxy 的接口全名写错了,或者方法签名对不上。华为 SDK 不同版本的回调接口名可能不一样,比如有的版本是 AuthResultListener,有的版本是 HuaweiIdAuthResultListener。

解决:把 AAR 解压,用 jadx 或 Android Studio 打开,找到回调接口的完整类名和方法签名,照着写。不要凭记忆或旧文档写,版本差异很容易在这里翻车。

5.3 现象:支付成功但没发货,或者重复发货

原因:客户端拿到购买结果后直接发货,没有经过服务器校验。或者服务器校验时没有做幂等处理,同一个订单号多次请求都返回成功。

解决:客户端只负责发起支付和把购买令牌传给服务器,发货逻辑全部放在服务器。服务器用购买令牌调华为的校验接口,校验通过后先查订单号是否已处理,没处理过才发货,处理过直接返回成功。这样即使客户端重复提交,也不会重复发货。

5.4 现象:Gradle 打包报 Duplicate class 或 Program type already present

原因:华为 SDK 依赖的某个库和 Unity 内置的库版本冲突,比如 okhttp、gson 这些。或者多个 AAR 之间引用了同一个库的不同版本。

解决:在 mainTemplate.gradle 里用 exclude 排除冲突的模块,或者用 resolutionStrategy 强制指定版本。具体排哪个,看依赖树里哪个包出现了两次。这个没有通用答案,每次冲突的包可能都不一样。

5.5 现象:华为设备上正常,非华为设备上登录失败

原因:非华为设备没有预装 HMS Core,HuaweiApiAvailability 返回非 0,登录自然失败。

解决:在 Init 阶段就检查 HMS Core 是否可用,不可用就引导用户去应用市场安装,或者直接走游客登录兜底。不要假设所有安卓设备都有 HMS Core,这个假设在真机上一定会被打脸。

6. 进阶技巧:把华为 SDK 封装成可替换的渠道层

接入华为 SDK 只是第一步,真正做发行的时候,你不可能只接华为一家。小米、OPPO、vivo、应用宝,每家都有自己的 SDK,接口设计各不相同。如果每接一家就把游戏逻辑改一遍,维护成本会爆炸。我的习惯是在接入第一家的时候就抽象一层渠道接口,把登录、支付、退出、上报这几个方法定义好,华为 SDK 只是其中一个实现。

public interface IChannelSDK { void Init(); void Login(Action<string> onSuccess, Action<int, string> onFailure); void Pay(string productId, string orderId, Action<bool> onResult); void Logout(); void ReportEvent(string eventName, Dictionary<string, string> params); }

华为的实现类叫 HuaweiChannel,小米的叫 XiaomiChannel,游戏逻辑只依赖 IChannelSDK,不依赖具体实现。切换渠道的时候只需要换一个实现类,游戏代码一行不用改。这个抽象层还有一个好处:编辑器里可以写一个 EditorChannel,所有方法都返回模拟数据,方便在编辑器里跑完整流程。

验证渠道层是否抽象干净,有一个简单的标准:把华为的 AAR 全部删掉,游戏还能在编辑器里正常跑,只是登录和支付走模拟数据。如果删掉 AAR 就编译不过,说明抽象层没做干净,游戏逻辑里还有直接引用华为类的地方。

我自己的习惯是每接一个新渠道,先写一个空的实现类,把所有方法都抛 NotImplementedException,然后跑一遍游戏,看哪些地方会崩。崩的地方就是耦合点,一个个改掉,直到游戏能完整跑通。这个过程通常要来回好几轮,但做完之后,后面再接新渠道就是填实现类的事,半天就能搞定一个。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询