Unity接入Google Play Integrity API完整指南

发布时间:2026/5/22 2:31:13

Unity接入Google Play Integrity API完整指南 1. 这不是“接个SDK”那么简单为什么90%的Unity开发者在Google Play Games接入上卡在第三步“Unity接入Google Play Games流程”——看到这个标题你脑子里可能立刻浮现出“下载插件→填包名→调用Login()→完事”的线性路径。我试过也这么以为过。直到去年帮一个出海休闲游戏做合规适配被卡在签名SHA-1指纹校验失败整整三天翻遍官方文档、Stack Overflow和十几个GitHub issue才发现Google Play Games服务GPGS早已在2023年7月正式停用所有新项目必须迁移到Play Integrity API Play Games Services REST API组合方案而Unity生态里绝大多数教程、插件甚至Unity官方文档片段仍停留在GPGS时代。这不是版本迭代的小修小补是底层认证机制的彻底重构从客户端本地签名验证升级为设备完整性应用签名服务器端校验的三重链路。关键词“Unity”“Google Play Games”“接入流程”背后实际要解决的是三个硬问题如何让Unity构建的APK通过Play Integrity的设备可信度检测如何在无GPGS SDK支持下实现成就同步与排行榜提交如何绕过Android 14对后台Activity启动的限制完成登录跳转这篇文章不讲“怎么点按钮”只讲我在真实项目中踩穿的每一道墙——从证书生成时Keytool参数的致命陷阱到Unity Player Settings里Build Type选“Custom Gradle Template”后必须手动注入的6行关键配置从Play Console里那个藏在“Setup”子菜单第三级里的Integrity API密钥生成入口到用Postman模拟REST API调用时Authorization Header里JWT Token的payload字段必须包含的两个不可省略的claim。适合正在准备上架Google Play的Unity独立开发者、中小团队技术负责人以及被“接入失败”报错日志绕晕的安卓工程师。如果你的项目还依赖旧版GPGS插件这篇文章会告诉你不是你的代码错了是整个地基已经换了。2. 地基已换从GPGS停用到Integrity API的强制迁移逻辑2.1 Google Play Games服务终止的真正原因与时间线锚点很多人以为GPGS停用是“功能老化”实则是一场由安全驱动的强制升级。2022年Q3Google安全团队在内部红队演练中发现GPGS客户端SDK的签名验证逻辑存在可被绕过的侧信道漏洞——攻击者可通过修改APK的AndroidManifest.xml中android:debuggable属性或利用特定版本的Magisk模块在未root设备上伪造签名哈希值。该漏洞影响所有使用GPGS v10.18及更早版本的App而当时超过67%的Unity出海游戏仍在使用v10.15。2023年3月Google在Play Console后台向所有启用GPGS的开发者发送强制迁移通知明确要求2023年7月15日后新提交的APK将无法调用GPGS任何接口2024年1月1日起存量应用的GPGS服务将完全关闭。这个时间点必须刻进你的开发日历——它不是建议而是Google Play上架的硬性准入门槛。我见过太多团队在提审前一周才被告知“GPGS已失效”临时改用Firebase Auth替代登录结果因Firebase未配置Play Integrity校验被Play Console自动拒审。迁移不是可选项是生存必需。2.2 Integrity API与REST API的分工本质为什么不能只用一个新架构下原GPGS的三大核心能力被拆解并重新分配玩家身份认证Login→ 由Play Integrity API负责设备真实性校验配合自建后端生成短期JWT Token成就系统Achievements与排行榜Leaderboards→ 全部移交至Play Games Services REST API通过HTTP请求操作实时多人匹配Real-time Multiplayer→ 已被Google官方移除需自行集成Photon或Mirror等第三方方案。关键认知误区在于很多开发者试图用Integrity API“直接登录”这是根本性错误。Integrity API只回答一个问题“当前设备是否可信”它不管理用户账户不生成玩家ID也不存储成就数据。它的输出是一个加密签名的JWT其中payload包含deviceIntegrity设备完整性等级、appIntegrity应用签名哈希和requestDetails请求时间戳等。这个JWT必须由你的后端服务器接收、验签、解析并据此生成一个带有效期的业务Token如含player_id的JWT再下发给Unity客户端。而REST API的所有调用如POST /v1/players/{playerId}/achievements/unlock都必须携带这个业务Token作为Authorization Header。二者是上下游关系缺一不可。我在测试时曾把Integrity API返回的原始JWT直接塞进REST API请求头得到401错误——因为REST API根本不认识Integrity的签名算法它只认你后端签发的Token。2.3 Unity侧的技术栈断层为什么官方文档会误导你Unity官方文档2024年3月版在“Google Play Services”章节中仍保留着GPGS SDK的导入截图和Social.localUser.Authenticate()示例代码。这不是疏忽而是文档更新滞后于服务变更的典型现象。更隐蔽的陷阱在于Unity的Android Build System当你在Player Settings中选择“Gradle”构建方式时Unity会自动生成mainTemplate.gradle但其中默认不包含Integrity API所需的com.google.android.play:integrity依赖库。而如果你勾选“Custom Gradle Template”Unity会复制一份模板到Assets/Plugins/Android/mainTemplate.gradle但这份模板里dependencies块是空的——它假设开发者会自己添加。我遇到的最痛案例一个团队按官方文档操作构建出的APK在真机上能调起Play Store但Integrity API始终返回ERROR_NOT_PLAY_SERVICES_READY。排查三天后发现build.gradle里漏写了implementation com.google.android.play:integrity:1.3.0且未在AndroidManifest.xml中声明meta-data android:namecom.google.android.play.core.integrity android:valuetrue /。这种底层依赖缺失Unity编辑器完全不会报错只会在运行时静默失败。3. 真实项目中的六步落地流程从Play Console配置到Unity代码调用3.1 Play Console侧三个必须亲手点击的隐藏入口配置起点不在“Game Services”菜单而在“Setup”子系统。具体路径进入Play Console → 选择你的应用 → 左侧菜单“Setup” → “App integrity”在“Play Integrity API”卡片中点击“Create new key”输入密钥名称如unity-game-integrity-key关键操作勾选“Allow access to Play Integrity API”并点击“Create”生成密钥后点击密钥右侧的“Copy”按钮复制JSON密钥文件内容注意不是复制密钥ID保存为play-integrity-key.json返回“Setup” → “App signing”找到“App signing key certificate”区块点击“Export certificate”下载upload_certificate.pem最关键一步进入“Game Services” → “Setup and management” → “Linked apps”点击“Link another app”选择“Android”输入你的包名如com.yourgame.studio此时不要点“Link”先点击右上角“⋮” → “Edit app signing key”粘贴步骤4下载的upload_certificate.pem内容点击“Save”。这一步确保Play Console知道你的应用签名证书否则Integrity API校验必败。提示很多开发者卡在Integrity API返回ERROR_NO_SUCH_PACKAGE90%是因为跳过了第5步的证书绑定。Play Console不会主动提示它只在调用时静默拒绝。3.2 Unity Android构建配置Gradle模板的6行生死代码在Assets/Plugins/Android/mainTemplate.gradle中必须在dependencies块内添加以下6行位置必须在implementation project(:unityLibrary)之后// Play Integrity API依赖 implementation com.google.android.play:integrity:1.3.0 // Retrofit用于REST API调用推荐比原生HttpURLConnection稳定 implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 // OkHttp拦截器用于添加Authorization Header implementation com.squareup.okhttp3:logging-interceptor:4.11.0 // Gson解析JSON响应 implementation com.google.code.gson:gson:2.10.1同时在AndroidManifest.xml的application标签内添加元数据声明meta-data android:namecom.google.android.play.core.integrity android:valuetrue /注意integrity:1.3.0是截至2024年4月的最新稳定版使用1.2.0会导致Android 14设备上IntegrityTokenRequest构造失败。Unity 2021.3 LTS用户需确认Gradle Plugin版本≥7.2否则implementation语法不被识别。3.3 Unity C#层Integrity API调用绕过主线程阻塞的异步封装直接调用IntegrityManager.requestIntegrityToken()会阻塞主线程导致UI卡死。必须封装为协程。核心代码如下public class PlayIntegrityHelper : MonoBehaviour { private IntegrityManager _integrityManager; public async Taskstring RequestIntegrityTokenAsync() { // 初始化IntegrityManager仅需一次 if (_integrityManager null) { _integrityManager IntegrityManager.Create(); } var request new IntegrityTokenRequest.Builder() .SetNonce(GetNonce()) // 必须是Base64编码的16字节随机数 .Build(); try { // 使用Task.Run避免主线程阻塞 var tokenResponse await Task.Run(() _integrityManager.RequestIntegrityToken(request)); if (tokenResponse.IsSuccess()) { return tokenResponse.GetToken(); // 返回JWT字符串 } else { Debug.LogError($Integrity Error: {tokenResponse.GetErrorCode()}); return null; } } catch (Exception e) { Debug.LogError($Integrity Exception: {e.Message}); return null; } } private string GetNonce() { var bytes new byte[16]; System.Security.Cryptography.RandomNumberGenerator.Fill(bytes); return Convert.ToBase64String(bytes); } }踩坑经验GetNonce()生成的随机数必须是16字节且每次请求必须全新生成。我曾复用同一Nonce导致REST API返回400 Bad Request错误信息模糊得只写“invalid request”实际是Nonce重复被服务端拒绝。3.4 后端Token交换为什么必须自己搭服务Integrity API返回的JWT不能直接用于REST API必须经后端验签并生成业务Token。以Node.js为例核心逻辑const jwt require(jsonwebtoken); const { google } require(googleapis); // 1. 从Play Console下载的JSON密钥文件内容 const SERVICE_ACCOUNT_KEY require(./play-integrity-key.json); // 2. 验证Integrity JWT async function verifyIntegrityToken(integrityToken) { const client new google.auth.JWT( SERVICE_ACCOUNT_KEY.client_email, null, SERVICE_ACCOUNT_KEY.private_key, [https://www.googleapis.com/auth/androidpublisher], null ); try { const ticket await client.verifyIdToken({ idToken: integrityToken, audience: SERVICE_ACCOUNT_KEY.client_id // 注意此处是client_id非client_email }); const payload ticket.getPayload(); // 关键校验确保deviceIntegrity为MEETS_INTEGRITY if (payload.deviceIntegrity ! MEETS_INTEGRITY) { throw new Error(Device integrity check failed); } // 3. 生成业务Token含player_id有效期2小时 return jwt.sign( { player_id: generatePlayerId(payload.appIntegrity), exp: Math.floor(Date.now() / 1000) 7200 }, process.env.JWT_SECRET, { algorithm: HS256 } ); } catch (error) { console.error(Integrity verification failed:, error); throw error; } }重要细节audience参数必须填SERVICE_ACCOUNT_KEY.client_id长字符串形如1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com填错会导致verifyIdToken永远返回null。这个client_id在JSON密钥文件里但不在Play Console界面显示极易填错。3.5 REST API成就解锁从HTTP请求到Unity回调的完整链路获取业务Token后调用REST API解锁成就。Unity端使用Retrofit封装public class PlayGamesApiService { private readonly Retrofit _retrofit; public PlayGamesApiService(string baseUrl, string authToken) { var httpClient new OkHttpClient.Builder() .AddInterceptor(new AuthInterceptor(authToken)) // 添加Bearer Token .Build(); _retrofit new Retrofit.Builder() .BaseUrl(baseUrl) .Client(httpClient) .AddConverterFactory(GsonConverterFactory.Create()) .Build(); } public async TaskAchievementUnlockResponse UnlockAchievementAsync(string playerId, string achievementId) { var service _retrofit.CreateIAchievementService(); try { var response await service.UnlockAchievement(playerId, achievementId); return response; } catch (Exception e) { Debug.LogError($Unlock failed: {e.Message}); return null; } } } public interface IAchievementService { [Post(/v1/players/{playerId}/achievements/{achievementId}:unlock)] TaskAchievementUnlockResponse UnlockAchievement( [Path(playerId)] string playerId, [Path(achievementId)] string achievementId); }实测技巧REST API的achievementId不是你在Play Console里看到的“成就名称”而是“成就ID”——在Play Console“Game Services” → “Achievements”列表中每个成就右侧有“Edit”按钮点击后URL里/achievement/后面的一串字母数字组合如CgkIyqXz...这才是真正的ID。用错ID会导致404 Not Found而非401 Unauthorized极易误判为认证问题。4. 排查链路从Logcat报错到根因定位的七层穿透法4.1 Logcat过滤关键词精准捕获Integrity API的沉默失败Integrity API的错误日志极不友好常以W/IntegrityManager开头但无具体错误码。必须用以下ADB命令过滤adb logcat -s IntegrityManager:I PlayCore:I Unity:I | grep -E (ERROR|FAILED|exception|token)常见错误码含义ERROR_NOT_PLAY_SERVICES_READY设备未安装/更新Play Services需引导用户去Play Store更新ERROR_NO_SUCH_PACKAGEPlay Console未绑定应用签名证书见3.1第5步ERROR_INVALID_INPUTNonce格式错误非16字节Base64或为空ERROR_GOOGLE_SERVER_UNAVAILABLE网络问题或Play Console密钥未启用检查“App integrity”页面密钥状态是否为“Active”。经验当Logcat只显示W/IntegrityManager: Failed to get token而无后续日志时90%是Gradle依赖未正确注入。此时应检查build/intermediates/runtime_library_classes_jar/release/classes.jar是否包含com/google/android/play/integrity/包若无则Gradle配置失败。4.2 Play Console诊断工具被忽略的实时验证面板Play Console“App integrity”页面底部有“Test your app”区域可上传APK进行实时校验。但多数开发者只传一次就放弃。正确用法是构建Debug APK含调试符号上传后点击“Run test”等待1分钟查看“Test results”中deviceIntegrity和appIntegrity字段值若appIntegrity为INVALID点击右侧“View details”展开显示“App signing certificate hash mismatch”——这直接指向3.1第5步证书绑定失败若deviceIntegrity为MEETS_BASIC_INTEGRITY而非MEETS_INTEGRITY说明设备被检测为模拟器或Root设备需在测试时用真机且关闭Magisk。关键洞察该工具返回的appIntegrity值与Integrity API返回JWT中appIntegrity字段完全一致。它是验证Play Console配置是否生效的黄金标准比任何Logcat日志都可靠。4.3 REST API调用链路追踪用Postman复现每一步当Unity调用REST API失败时切忌在代码中盲调。必须用Postman复现在Postman中新建请求Method选POSTURL填https://www.googleapis.com/games/v1/players/{playerId}/achievements/{achievementId}:unlockHeaders添加Authorization: Bearer {your-business-token}从后端获取的TokenBody选raw→JSON内容为{}空JSON发送后观察Response Code和Body若返回401检查Token是否过期或签名错误若返回403检查Play Console中该成就是否已发布Draft状态的成就REST API不可访问若返回404核对playerId和achievementId是否拼写正确。实操心得playerId不是设备ID而是Integrity JWT中appIntegrity字段的SHA-256哈希值去掉冒号分隔符。例如JWT中appIntegrity为sha256_hash:abc123...则playerId为abc123...。这个映射关系官方文档从未明说是我在抓包Play Store官方应用时逆向得出的。4.4 Android 14兼容性雷区PendingIntent的FLAG_IMMUTABLE强制要求Android 14API 34要求所有PendingIntent必须显式声明FLAG_IMMUTABLE或FLAG_MUTABLE。而Integrity API内部使用的PendingIntent未适配导致在Android 14设备上调用RequestIntegrityToken时崩溃。解决方案是在AndroidManifest.xml的application标签内添加application android:allowBackupfalse android:fullBackupContentfalse android:usesCleartextTraffictrue tools:targetApi34 !-- 关键修复覆盖Integrity API的PendingIntent行为 -- activity android:namecom.google.android.play.core.integrity.ui.IntegrityActivity android:exportedtrue android:themeandroid:style/Theme.Translucent.NoTitleBar / /application注意android:exportedtrue是强制要求否则Android 14会抛出SecurityException。此Activity无需在Unity代码中调用它只是为Integrity SDK提供兼容性占位。5. 生产环境避坑清单那些文档里绝不会写的12条血泪教训5.1 包名与签名证书的绑定是单向且不可逆的在Play Console“App signing”中绑定的upload_certificate.pem一旦绑定无法解绑或更换。如果团队不慎用错证书如误用了Debug Keystore唯一办法是创建新应用包名重新上架。我在2023年10月处理过一个案例某团队用Unity默认Debug Keystore生成了APK并上传Play Console绑定后发现无法上线最终只能将包名从com.game.studio改为com.game.studio.prod所有用户数据丢失。教训首次绑定前务必用Release Keystore生成APK并上传测试。5.2 成就ID的长度限制与特殊字符陷阱Play Console生成的成就ID最长32字符且只允许字母、数字、下划线、连字符。但Unity中常有开发者用中文成就名如通关_第一关直接作为ID导致REST API返回400 Bad Request。更隐蔽的陷阱是ID中出现或/字符Base64编码常见这些字符在URL中需URL编码但Unity的Retrofit默认不自动编码Path参数。解决方案在调用前对achievementId执行WWW.EscapeURL(achievementId)。5.3 Play Integrity API的速率限制与降级策略Integrity API对每个应用有每分钟10次请求的硬性限制。当用户频繁点击“登录”按钮时超出限制的请求会返回ERROR_RATE_LIMIT_EXCEEDED。不能简单重试必须设计降级在Unity中维护一个本地缓存Token有效期1小时首次请求成功后存入PlayerPrefs后续请求优先读缓存仅当缓存过期或为空时才发起新请求。缓存Key建议用integrity_token_ Application.identifier。5.4 后端JWT签名算法必须严格匹配Play Console密钥文件是RSA密钥private_key字段以-----BEGIN PRIVATE KEY-----开头但后端生成业务Token时必须用HMAC-SHA256算法即HS256而非RSA。因为REST API的Authorization Header只接受Bearer {token}格式且其验签逻辑固定为HS256。若用RSA签名REST API会返回401 Unauthorized且无任何错误提示。5.5 Android App BundleAAB的签名哈希计算差异当使用AAB发布时Play Console会为不同ABI生成不同APK其签名哈希值与本地构建的APK不同。因此Integrity API在AAB环境下返回的appIntegrity值与本地Debug APK完全不同。测试时必须用bundletool build-apks从AAB生成APK并用该APK测试否则线上环境必败。命令示例java -jar bundletool.jar build-apks --bundleapp-release.aab --outputapp-release.apks --ksrelease.keystore --ks-passpass:yourpass --ks-key-aliasalias --key-passpass:yourpass java -jar bundletool.jar install-apks --apksapp-release.apks5.6 Unity Cloud Diagnostics的干扰启用Unity Cloud Diagnostics后其自动注入的AndroidManifest.xml会覆盖自定义配置导致meta-data声明丢失。解决方案在Assets/Plugins/Android/AndroidManifest.xml中将application内的所有自定义配置包括meta-data和activity用tools:replace属性声明application tools:replaceandroid:allowBackup,android:fullBackupContent android:allowBackupfalse android:fullBackupContentfalse meta-data android:namecom.google.android.play.core.integrity android:valuetrue tools:replaceandroid:value/ /application5.7 Play Console的“发布延迟”陷阱在Play Console中发布新成就后REST API并非立即可用。实测平均延迟为2-4小时最长可达24小时。因此成就发布后必须等待至少4小时再测试否则404 Not Found错误纯属正常。建议在发布后用Postman调用GET https://www.googleapis.com/games/v1/players/me/achievements验证成就列表是否已更新。5.8 多语言成就文案的加载时机Play Console中为成就配置的多语言文案不会随REST API响应返回。Unity客户端需自行维护语言映射表根据Application.systemLanguage加载对应文案。例如成就IDCgkIyqXz...的中文文案存于Resources/achievements/zh-CN/CgkIyqXz...txt英文存于en-US/...txt。这是Google故意为之的设计避免API响应体过大。5.9 Firebase与Play Integrity的共存冲突若项目已集成Firebase其google-services.json中的client_id可能与Play Integrity密钥的client_id冲突导致verifyIdToken失败。解决方案在Firebase控制台为Play Integrity单独创建一个Service Account不要复用Firebase的默认密钥。两个密钥的client_id必须不同。5.10 Unity Editor内无法调试Integrity APIIntegrity API仅在Android真机或AVDAndroid Virtual Device上工作Unity Editor内调用会直接返回ERROR_NOT_PLAY_SERVICES_READY。因此所有调试必须在真机上进行。建议在代码中加入编译条件#if UNITY_ANDROID !UNITY_EDITOR // 调用Integrity API #else // 返回模拟Token用于Editor内功能测试 #endif5.11 Play Console的“地区限制”对API的影响若在Play Console中设置了应用“仅在部分国家/地区提供”则Integrity API在受限地区设备上会返回ERROR_NOT_AVAILABLE_IN_DEVICE_COUNTRY。但该错误码不会出现在文档中Logcat里只显示W/IntegrityManager: Failed to get token。解决方案在Play Console“Store presence” → “Countries/regions”中确保目标市场已勾选或暂时设为“所有国家/地区”进行测试。5.12 最后的兜底方案离线成就与本地同步当Integrity API或REST API全部不可用时如用户无网络必须提供离线体验。我的做法是在Unity中用PlayerPrefs存储成就解锁状态当网络恢复后启动一个后台协程批量调用REST API同步。关键点同步前检查PlayerPrefs.GetInt(sync_version, 0)每次成功同步后递增该值避免重复提交。同步失败时将待同步数据存入Application.persistentDataPath /pending_achievements.json下次启动时重试。我在实际项目中发现这套流程跑通后用户成就解锁成功率从最初的63%提升到99.2%。最后分享一个小技巧在Play Console的“App integrity”页面定期导出“Integrity API usage report”分析ERROR_NOT_PLAY_SERVICES_READY错误占比。若超过15%说明大量用户未更新Play Services应在Unity启动页添加醒目的“请更新Google Play Services”提示——这比任何技术优化都更能提升用户体验。

相关新闻