Unity游戏集成Android原生推送:从FCM到AAR库的实战指南
1. 项目概述为什么Unity游戏需要独立的Android推送库如果你是一个Unity开发者正在为你的移动游戏项目集成推送通知功能那么你很可能已经接触过Unity官方的Push Notifications SDK。它看起来很方便对吧在编辑器里点点设置写几行C#代码似乎就能搞定。但当你真正深入项目尤其是当你的游戏需要处理复杂的后台逻辑、自定义通知样式、或者需要与自己的服务器深度集成时你可能会发现官方方案的限制。比如当游戏进程被系统回收后如何保证通知能准确送达并触发游戏内的特定行为如何在不依赖Unity Analytics的情况下将通知点击事件与游戏内的奖励发放、活动跳转无缝对接这些需求往往催生了一个更底层、更可控的解决方案自己动手封装一个专属于你项目的Android原生推送库并通过Unity的AndroidJavaClass/AndroidJavaObject接口进行调用。这个项目的核心就是跳出Unity Services的“舒适区”直接与Android系统的推送机制如Firebase Cloud Messaging, FCM对话。它意味着更高的灵活性和控制权你可以精确管理通知的生命周期自定义通知渠道Notification Channel处理深链接Deep Link甚至在应用完全关闭的情况下通过后台服务Service或WorkManager来预处理通知数据。对于中重度游戏特别是那些依赖实时运营活动、有复杂用户召回体系的项目拥有一个自己完全掌控的推送底层是保障运营稳定性和用户体验的关键。接下来我将以一个实际集成FCM的Android AAR库项目为例拆解从零构建、到Unity集成的全流程。我会假设你已有基本的Android Studio和Unity开发经验我们将聚焦于那些官方文档不会细说的“坑”和实战技巧。2. 核心架构设计与思路拆解2.1 为何选择“原生库Unity桥接”模式直接使用Unity的C#层调用Android原生代码主要有两种方式通过AndroidJavaClass/AndroidJavaObject进行即时调用JNI或者将功能封装成Android Library (AAR)供Unity打包。对于推送这种需要后台服务、依赖复杂第三方SDK如Firebase的功能强烈推荐后者——AAR库模式。优势分析解耦与复用推送逻辑被封装在独立的Android模块中。无论是Unity项目还是未来可能有的其他原生Android项目都可以直接复用这个库。代码管理更清晰。依赖管理Firebase Messaging等第三方SDK的依赖关系可以在Android库的build.gradle中明确定义由Gradle统一管理避免Unity项目中出现依赖冲突或版本混乱。后台能力可以方便地创建FirebaseMessagingService等继承自Android系统组件的类这是实现可靠后台消息接收的基础。纯JNI调用很难优雅地实现这类组件。构建流程整合Unity在构建Android APK时会自动将AAR库解压并合并到最终的应用模块中流程顺畅。基本架构流Unity C# 脚本 (游戏逻辑层) ↓ (调用) Unity-Android 桥接层 (C#使用AndroidJavaClass) ↓ (JNI) 自定义 Android AAR 库 (Java/Kotlin) ↓ (SDK) Firebase Cloud Messaging (FCM) ↓ (云端) 你的推送服务器 / Firebase 控制台这个架构的核心在于那个“自定义Android AAR库”它承担了所有与Android系统及FCM交互的脏活累活并向Unity暴露一组简洁的接口。2.2 关键组件与职责划分一个健壮的推送库通常包含以下核心组件理解它们的关系至关重要统一入口类 (例如UnityPushManager)职责作为Unity调用的主要接口。提供初始化、获取设备Token、设置监听器等方法。通常设计为单例。关键点需要保存对Unity侧回调的引用。这里通常使用UnityPlayer.UnitySendMessage或AndroidJavaProxy来回调Unity后文会详细对比。Firebase消息服务类 (继承自FirebaseMessagingService)职责接收FCM服务器下发的消息。重写onNewToken处理Token刷新重写onMessageReceived处理接收到的消息负载Payload。实操心得这是处理后台消息的核心。即使应用被杀死FCM也能唤醒这个服务取决于消息优先级和系统策略。在这里你需要决定是直接创建通知还是将数据传递给Unity游戏逻辑。通知构建器与渠道管理职责根据接收到的消息数据构建符合Android系统要求的通知Notification。自Android 8.0 (API 26) 起通知渠道 (Notification Channel)是强制要求必须在显示第一条通知前创建。关键点渠道一旦创建其部分属性如重要性在系统设置中由用户控制程序无法再修改。因此渠道ID和名称的设计需要慎重考虑。数据序列化与通信协议职责定义Unity与Android库之间、以及服务器与FCM之间传递数据的格式。通常使用JSON。关键点需要设计一个双方都能理解的协议。例如消息负载中可以包含一个unity_action字段告诉Unity点击通知后应该打开哪个游戏界面或执行什么函数。3. 实操步骤一创建Android推送库(AAR)3.1 环境准备与项目创建安装Android Studio确保已安装最新稳定版。在SDK Manager中安装你目标API级别例如API 33的SDK以及对应的“Google Play服务”和“Firebase”相关组件。创建新项目打开Android Studio选择“New Project”。模板选择“Empty Views Activity”。注意这里我们创建的是一个可独立运行调试的App模块库模块将包含在其中。项目命名例如UnityAndroidPushLib包名建议为com.yourcompany.unitypush。语言选择Kotlin推荐代码更简洁或Java。最低API Level根据你的Unity项目设置选择通常不低于API 21 (Android 5.0)。3.2 添加Firebase与关键依赖关联Firebase项目在Android Studio中点击菜单栏Tools-Firebase。在Assistant面板中选择Cloud Messaging点击Set up Firebase Cloud Messaging。按照向导连接到你已有的Firebase项目或创建一个新的。这一步会自动下载google-services.json配置文件到你的app模块根目录并修改项目级build.gradle和模块级build.gradle。这是后续一切工作的基础务必成功。修改模块级build.gradle打开app目录下的build.gradle.kts或build.gradle。确保已应用Google服务插件plugins { id(com.android.application) id(org.jetbrains.kotlin.android) id(com.google.gms.google-services) // 确保这行存在 }在dependencies块中确保包含Firebase Messaging依赖dependencies { // ... 其他依赖 implementation(com.google.firebase:firebase-messaging-ktx:23.4.0) // 使用最新稳定版 implementation(com.google.code.gson:gson:2.10.1) // 用于JSON解析强烈推荐 }同步Gradle。3.3 实现核心Java/Kotlin代码现在我们在app模块下创建我们的库核心类。虽然最终产出是AAR但在开发阶段放在app模块内便于调试。创建UnityPushManager单例入口package com.yourcompany.unitypush import android.app.Activity import android.content.Context import com.google.firebase.messaging.FirebaseMessaging import org.json.JSONObject import java.lang.ref.WeakReference class UnityPushManager private constructor(context: Context) { companion object { private var instance: UnityPushManager? null private const val UNITY_GAME_OBJECT PushNotificationReceiver // Unity中接收消息的GameObject名 private const val UNITY_METHOD_TOKEN OnTokenReceived // Unity中接收Token的方法名 private const val UNITY_METHOD_MESSAGE OnMessageReceived // Unity中接收消息的方法名 fun getInstance(context: Context): UnityPushManager { return instance ?: synchronized(this) { instance ?: UnityPushManager(context.applicationContext).also { instance it } } } } private val appContext: Context context.applicationContext private var unityActivityRef: WeakReferenceActivity? null // 供Unity调用初始化并设置当前Activity fun initialize(activity: Activity) { unityActivityRef WeakReference(activity) // 可以在这里初始化其他组件如创建通知渠道 NotificationChannelHelper.createNotificationChannels(appContext) } // 供Unity调用获取当前FCM Token fun getFcmToken() { FirebaseMessaging.getInstance().token.addOnCompleteListener { task - if (task.isSuccessful) { val token task.result token?.let { sendTokenToUnity(it) } } else { // 处理获取Token失败的情况可以将错误信息回调给Unity Log.e(UnityPushManager, Fetching FCM registration token failed, task.exception) } } } // 内部方法将Token发送回Unity private fun sendTokenToUnity(token: String) { unityActivityRef?.get()?.runOnUiThread { try { // 使用UnitySendMessage回调 com.unity3d.player.UnityPlayer.UnitySendMessage( UNITY_GAME_OBJECT, UNITY_METHOD_TOKEN, token ) } catch (e: Exception) { Log.e(UnityPushManager, Failed to send token to Unity, e) } } } // **关键方法**供FirebaseMessagingService调用将消息转发给Unity fun forwardMessageToUnity(data: MapString, String) { val jsonData JSONObject(data).toString() unityActivityRef?.get()?.runOnUiThread { try { com.unity3d.player.UnityPlayer.UnitySendMessage( UNITY_GAME_OBJECT, UNITY_METHOD_MESSAGE, jsonData ) } catch (e: Exception) { Log.e(UnityPushManager, Failed to send message to Unity, e) } } } // 其他方法如处理通知点击设置标签等... }注意事项UnitySendMessage是Unity提供的原生接口用于从原生代码调用Unity场景中特定GameObject上的脚本方法。它的性能开销相对较大且是“发后即忘”的不适合高频调用但对于Token和消息回调完全足够。使用WeakReference持有Activity引用防止内存泄漏。所有回调务必切换到UI线程runOnUiThread执行因为Unity的交互必须在主线程。创建CustomFirebaseMessagingServicepackage com.yourcompany.unitypush import android.util.Log import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class CustomFirebaseMessagingService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) Log.d(FCM, Refreshed token: $token) // 当Token刷新时需要将其同步到你的服务器和Unity UnityPushManager.getInstance(applicationContext).sendTokenToUnity(token) // 同时你也应该将新Token发送到你自己的后端服务器 sendRegistrationToServer(token) } override fun onMessageReceived(remoteMessage: RemoteMessage) { Log.d(FCM, From: ${remoteMessage.from}) // 检查消息是否包含数据负载 remoteMessage.data.isNotEmpty().let { Log.d(FCM, Message data payload: ${remoteMessage.data}) // 场景1应用在前台或后台未被杀死直接交给Unity处理 handleDataMessage(remoteMessage.data) } // 检查消息是否包含通知负载这部分由系统自动处理显示通知 remoteMessage.notification?.let { Log.d(FCM, Message Notification Body: ${it.body}) // 场景2应用在后台或被杀系统会根据通知负载自动创建通知。 // 但如果我们想自定义通知或者需要即使应用被杀也能处理数据可以在这里创建通知。 // 更常见的做法是无论应用状态如何都根据data负载创建自定义通知。 sendNotification(remoteMessage.data, it.title, it.body) } // 如果消息既无data也无notification则忽略 } private fun handleDataMessage(data: MapString, String) { // 将数据传递给UnityPushManager由其转发给Unity游戏逻辑 UnityPushManager.getInstance(applicationContext).forwardMessageToUnity(data) } private fun sendNotification(data: MapString, String, title: String?, body: String?) { // 这里实现创建系统通知的逻辑 // 1. 创建NotificationChannel如果需要 // 2. 使用NotificationCompat.Builder构建通知 // 3. 为通知设置PendingIntent点击后跳转Activity并携带data数据 // 4. 使用NotificationManager.notify显示通知 // 注意PendingIntent中启动的Activity需要能解析data并传递给Unity } private fun sendRegistrationToServer(token: String) { // 实现将Token发送到你自己的应用服务器的逻辑 } }核心解析onMessageReceived是消息处理的枢纽。FCM消息有两种类型通知消息Notification和数据消息Data。两者可以并存。通知消息当应用在后台时系统会自动处理并显示通知栏消息。remoteMessage.notification不为空。数据消息系统不会自动处理。无论应用在前台还是后台都会触发onMessageReceived你需要自己处理remoteMessage.data。这是实现游戏内逻辑触发的关键比如收到一条数据消息{type:daily_reward, amount:100}你可以在handleDataMessage中解析并调用游戏内发奖励的接口。最佳实践发送同时包含notification和data的混合消息。这样当应用在后台时系统显示通知当用户点击通知或应用在前台时你都能在data中获取到自定义逻辑所需的信息。创建通知渠道助手NotificationChannelHelperpackage com.yourcompany.unitypush import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build import androidx.core.app.NotificationCompat object NotificationChannelHelper { const val CHANNEL_ID_GENERAL your_push_channel_general const val CHANNEL_NAME_GENERAL 常规通知 fun createNotificationChannels(context: Context) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { val notificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val generalChannel NotificationChannel( CHANNEL_ID_GENERAL, CHANNEL_NAME_GENERAL, NotificationManager.IMPORTANCE_HIGH // 重要性级别HIGH会发出声音 ).apply { description 游戏重要消息和活动通知 enableLights(true) lightColor android.graphics.Color.GREEN } notificationManager.createNotificationChannel(generalChannel) // 可以创建更多不同用途的渠道如IMPORTANCE_LOW的“新闻资讯”渠道 } } fun getNotificationBuilder(context: Context, channelId: String): NotificationCompat.Builder { return NotificationCompat.Builder(context, channelId) .setSmallIcon(android.R.drawable.ic_dialog_info) // 替换成你的应用图标 .setAutoCancel(true) } }在AndroidManifest.xml中注册服务 打开app/src/main/AndroidManifest.xml在application标签内添加service android:name.CustomFirebaseMessagingService android:exportedfalse intent-filter action android:namecom.google.firebase.MESSAGING_EVENT / /intent-filter /serviceandroid:exportedfalse表示此服务仅供本应用内部使用。intent-filter用于声明该服务响应FCM消息事件。3.4 生成AAR库文件我们的代码写在app模块里但我们需要将其编译成独立的AAR库供Unity使用。更规范的做法是创建一个Android Library模块。这里简述步骤在Android Studio中File-New-New Module选择Android Library命名为pushlib。将之前在app模块中编写的核心Java/Kotlin代码UnityPushManager,CustomFirebaseMessagingService,NotificationChannelHelper移动到pushlib模块的对应源码目录下。将app模块build.gradle中关于Firebase Messaging和Gson的依赖复制到pushlib模块的build.gradle中。确保pushlib模块的AndroidManifest.xml也包含了CustomFirebaseMessagingService的声明。在项目根目录的settings.gradle中确保包含了pushlib模块。在Android Studio右侧的Gradle面板中找到pushlib-Tasks-build-assemble。双击运行或使用命令行./gradlew :pushlib:assembleRelease。构建完成后在pushlib/build/outputs/aar/目录下找到pushlib-release.aar或debug版本这就是我们需要的库文件。关键检查点确保AAR文件中包含了编译后的classes.jar、AndroidManifest.xml以及可能的资源文件。可以用解压软件打开查看。4. 实操步骤二Unity项目集成与桥接4.1 Unity项目设置与AAR导入开启Android支持在Unity Editor中File-Build Settings切换平台到Android。如果没有安装Android Build Support模块需要先安装。Player Settings配置Other Settings-IdentificationPackage Name必须与Android库中使用的包名一致如com.yourcompany.unitypush或在它的子包下。Minimum API Level与Android库项目设置保持一致。Publishing Settings-Minify如果使用Proguard/R8混淆需要谨慎配置规则避免混淆与Unity交互的Native接口。建议在测试阶段先关闭。导入AAR文件在Unity项目的Assets目录下建议创建Plugins/Android子目录将上一步生成的pushlib-release.aar文件复制进来。同时必须将Firebase项目配置文件google-services.json也复制到Assets/Plugins/Android目录下。Unity在构建时会将其打包进APK。处理Android依赖关键且易错AAR文件本身不包含其依赖的第三方库如Firebase Messaging。Unity不会自动解析Gradle依赖。解决方案创建一个名为mainTemplate.gradle的文件Unity 2019.3支持放在Assets/Plugins/Android目录下。Unity在构建时会以此文件为基础生成最终的Gradle构建脚本。mainTemplate.gradle内容示例dependencies { implementation fileTree(dir: libs, include: [*.jar, *.aar]) // 添加你的AAR库所依赖的第三方库 implementation com.google.firebase:firebase-messaging-ktx:23.4.0 implementation com.google.code.gson:gson:2.10.1 // 其他Unity项目可能需要的通用依赖 implementation androidx.core:core-ktx:1.12.0 implementation androidx.appcompat:appcompat:1.6.1 }此外可能还需要创建baseProjectTemplate.gradle来配置仓库等。更现代的做法是使用Unity的Custom Gradle Template功能。4.2 编写C#桥接脚本在Unity中创建C#脚本例如AndroidPushBridge.cs这是连接Unity逻辑和Android原生库的桥梁。using UnityEngine; using System; using System.Collections.Generic; public class AndroidPushBridge : MonoBehaviour { private static AndroidPushBridge _instance; public static AndroidPushBridge Instance _instance; private AndroidJavaObject _unityPushManager; private AndroidJavaClass _unityPlayerClass; private AndroidJavaObject _currentActivity; // 定义回调委托 public Actionstring OnFcmTokenReceived; public ActionDictionarystring, string OnPushMessageReceived; void Awake() { if (_instance ! null _instance ! this) { Destroy(gameObject); return; } _instance this; DontDestroyOnLoad(gameObject); // 常驻场景用于接收回调 InitializeAndroidBridge(); } void InitializeAndroidBridge() { try { // 获取当前Unity的Activity上下文 _unityPlayerClass new AndroidJavaClass(com.unity3d.player.UnityPlayer); _currentActivity _unityPlayerClass.GetStaticAndroidJavaObject(currentActivity); // 定位我们的原生库中的管理器类 AndroidJavaClass managerClass new AndroidJavaClass(com.yourcompany.unitypush.UnityPushManager); // 调用静态方法获取单例传入Activity上下文 _unityPushManager managerClass.CallStaticAndroidJavaObject(getInstance, _currentActivity); // 初始化原生管理器 _unityPushManager?.Call(initialize, _currentActivity); Debug.Log([AndroidPushBridge] Initialized successfully.); } catch (Exception e) { Debug.LogError($[AndroidPushBridge] Initialization failed: {e.Message}\n{e.StackTrace}); } } // 供游戏逻辑调用请求获取FCM Token public void RequestFcmToken() { if (_unityPushManager null) { Debug.LogWarning([AndroidPushBridge] Manager not initialized. Cannot request token.); return; } _unityPushManager.Call(getFcmToken); } // **关键**供Android原生代码回调的方法必须为public且名称匹配 public void OnTokenReceived(string token) { Debug.Log($[AndroidPushBridge] FCM Token received: {token}); OnFcmTokenReceived?.Invoke(token); // 这里可以将Token发送给你的游戏服务器 // StartCoroutine(SendTokenToGameServer(token)); } // **关键**供Android原生代码回调的方法接收JSON字符串 public void OnMessageReceived(string jsonData) { Debug.Log($[AndroidPushBridge] Push message received: {jsonData}); try { // 使用简单的JSON解析对于复杂结构建议使用JsonUtility或第三方库如Newtonsoft.Json // 这里示例解析为字典 var dataDict JsonUtility.FromJsonSerializableDictionary(jsonData)?.ToDictionary(); // 或者如果你使用了Gson的格式可能需要更复杂的解析。假设原生层传递的是简单键值对JSON。 // 一个更健壮的方式是让原生层和C#层约定一个简单的序列化格式。 OnPushMessageReceived?.Invoke(dataDict); } catch (Exception e) { Debug.LogError($[AndroidPushBridge] Failed to parse message data: {e.Message}); } } void OnDestroy() { _unityPushManager?.Dispose(); _currentActivity?.Dispose(); _unityPlayerClass?.Dispose(); } // 一个辅助类用于解析简单的JSON对象到Dictionary [System.Serializable] private class SerializableDictionary { public ListKeyValuePair items new ListKeyValuePair(); public Dictionarystring, string ToDictionary() { var dict new Dictionarystring, string(); foreach (var item in items) { dict[item.key] item.value; } return dict; } } [System.Serializable] private class KeyValuePair { public string key; public string value; } }将这个脚本挂载到一个GameObject上并将该GameObject命名为PushNotificationReceiver与原生代码中的UNITY_GAME_OBJECT常量保持一致并确保它在初始场景中且不被销毁。4.3 在游戏逻辑中调用在需要获取Token或处理推送消息的地方例如游戏启动管理器或登录控制器中public class GameManager : MonoBehaviour { void Start() { // 获取桥接实例 var pushBridge AndroidPushBridge.Instance; // 订阅事件 if (pushBridge ! null) { pushBridge.OnFcmTokenReceived HandleToken; pushBridge.OnPushMessageReceived HandlePushMessage; // 请求Token pushBridge.RequestFcmToken(); } else { Debug.LogError(AndroidPushBridge instance not found!); } } void HandleToken(string token) { Debug.Log($GameManager: Token ready - {token}); // 将Token存储到PlayerPrefs或发送到你的游戏服务器 PlayerPrefs.SetString(FCM_TOKEN, token); // SendToServer(token); } void HandlePushMessage(Dictionarystring, string messageData) { Debug.Log($GameManager: Push message handled.); if (messageData.TryGetValue(type, out string type)) { switch (type) { case daily_reward: if (messageData.TryGetValue(amount, out string amountStr) int.TryParse(amountStr, out int amount)) { // 游戏内发放奖励 CurrencyManager.Instance.AddCoins(amount); UIManager.Instance.ShowRewardPopup(amount); } break; case open_screen: if (messageData.TryGetValue(screen_name, out string screenName)) { // 导航到特定游戏界面 UIManager.Instance.NavigateTo(screenName); } break; default: Debug.Log($Unknown message type: {type}); break; } } } void OnDestroy() { var pushBridge AndroidPushBridge.Instance; if (pushBridge ! null) { pushBridge.OnFcmTokenReceived - HandleToken; pushBridge.OnPushMessageReceived - HandlePushMessage; } } }5. 构建、测试与问题排查实录5.1 完整构建流程与签名环境检查JDK版本Unity通常捆绑了OpenJDK确保其路径在Unity设置Edit-Preferences-External Tools中正确配置。Android SDK NDK路径同样在External Tools中设置正确。构建配置Build Settings中选择Export Project选项。这会将项目导出为Android Studio工程方便排查复杂的Gradle问题。首次集成时强烈建议勾选。选择Build或Export。如果导出会生成一个文件夹用Android Studio打开其中的build.gradle文件进行最终构建和签名。签名APK在Unity中Player Settings-Publishing Settings配置Keystore或使用Android Studio进行签名。重要用于测试推送的APK其签名必须与在Firebase控制台注册应用时上传的签名证书指纹SHA-1或SHA-256一致。调试版本使用调试密钥库发布版本使用发布密钥库。5.2 推送测试全流程获取设备Token在Unity中运行游戏或安装构建好的APK到真机。查看Logcat日志使用Android Studio的Logcat工具或adb logcat命令过滤标签UnityPushManager或FCM找到打印的Token。它是一长串字符串。从服务器发送测试消息使用Firebase控制台最简单。进入你的Firebase项目 - Engage - Cloud Messaging - 创建你的第一条通知。在“测试消息”部分输入刚才获取的设备Token发送。你可以选择发送“通知消息”、“数据消息”或两者混合。使用cURL或Postman更灵活适合测试数据消息。curl -X POST \ https://fcm.googleapis.com/fcm/send \ -H Authorization: keyYOUR_SERVER_KEY \ -H Content-Type: application/json \ -d { to: DEVICE_FCM_TOKEN, priority: high, data: { type: daily_reward, amount: 100, title: 奖励提醒, body: 您的每日奖励已到账 }, notification: { title: 奖励提醒, body: 您的每日奖励已到账, sound: default } }YOUR_SERVER_KEY在Firebase控制台项目设置 - 云消息传递 - 服务器密钥。DEVICE_FCM_TOKEN步骤1中获取的Token。这个例子发送了一条混合消息既有data供应用逻辑处理也有notification供系统显示。观察结果应用在前台应该能在Logcat中看到onMessageReceived被触发并且Unity的OnMessageReceived回调被执行游戏内逻辑如发奖励应被触发。通知栏可能不会显示取决于系统行为Android高版本可能不显示但数据已收到。应用在后台系统应显示通知栏消息。点击通知后应用应被唤醒并且CustomFirebaseMessagingService中onMessageReceived被调用如果data字段存在数据最终应能传递到Unity。应用进程被杀死系统应显示通知栏消息。点击通知后应用重启需要在MainActivity的onCreate或onNewIntent中处理从通知Intent中携带过来的data并尽快传递给Unity。这部分逻辑需要在Android原生端额外处理。5.3 常见问题与排查技巧下表总结了集成过程中最常见的“坑”及其解决方案问题现象可能原因排查步骤与解决方案获取不到Token或onNewToken不回调1.google-services.json文件未正确放置或内容错误。2. 设备未安装或未更新Google Play服务。3. 项目包名与Firebase注册的不一致。4. 网络问题如设备在墙内。1. 检查google-services.json是否在Assets/Plugins/Android下并确认其中的package_name与Unity的Package Name一致。2. 在真机上测试确保Google Play服务可用。模拟器可能需要安装GMS镜像。3. 在Firebase控制台核对应用包名。4. 确保测试设备网络通畅。能收到Token但收不到推送消息1. 服务器发送消息的API密钥或Token错误。2. 消息格式不正确或data字段为空。3. Android端CustomFirebaseMessagingService未在AndroidManifest.xml中正确注册。4. 应用被系统深度优化电池优化限制了后台活动。1. 核对服务器密钥和Token。用Firebase控制台发送测试消息最可靠。2. 使用上文cURL示例格式发送混合消息测试。3. 检查AndroidManifest.xml确保服务声明正确且包名无误。4. 引导用户将应用从电池优化白名单中移除。应用在前台能收到data但在后台/被杀时收不到1. 发送的是纯data消息。当应用在后台时系统不会自动唤醒应用处理纯data消息除非消息优先级很高且满足特定条件。2. 通知点击后数据未从Intent传递到Unity。1.始终发送混合消息同时包含notification和data。notification确保通知栏显示和点击行为。2. 在Android原生端需要在MainActivity中重写onCreate和onNewIntent检查Intent中的extras并将其转发给UnityPushManager。Unity回调不执行OnTokenReceived/OnMessageReceived1. Unity中接收回调的GameObject名称或方法名称与原生代码中UnitySendMessage调用不匹配。2. Unity脚本未挂载到场景中或对象被销毁。3. 原生代码回调未在UI线程执行。1. 仔细核对UNITY_GAME_OBJECT和UNITY_METHOD_*常量与Unity场景中的对象名、方法名是否完全一致大小写敏感。2. 确保挂载AndroidPushBridge的GameObject在初始场景中且是DontDestroyOnLoad的。3. 确保原生代码中调用UnitySendMessage是在runOnUiThread中。构建失败Gradle报错1. 依赖冲突如多个不同版本的Firebase库。2.mainTemplate.gradle配置错误。3. 缺少必要的仓库声明。1. 使用./gradlew :app:dependencies命令在导出后的Android Studio工程中查看依赖树解决冲突。2. 检查mainTemplate.gradle语法确保依赖项名称和版本号正确。3. 在baseProjectTemplate.gradle或mainTemplate.gradle的repositories块中添加google()和mavenCentral()。通知不显示或没有声音1. 未针对Android 8.0创建通知渠道Notification Channel。2. 渠道的重要性Importance设置过低。3. 应用的通知权限被用户关闭。1. 确保在应用初始化时如UnityPushManager.initialize中调用了创建通知渠道的代码。2. 对于重要通知使用IMPORTANCE_HIGH。3. 在应用内引导用户开启通知权限。可以通过NotificationManager.areNotificationsEnabled()检查。独家避坑技巧双Token验证设备FCM Token可能会变如应用卸载重装、用户清除数据。因此每次启动应用都应重新获取并上报Token给服务器。服务器端发送推送前最好用当前有效的Token列表。后台消息处理策略对于“应用被杀”的情况单纯依赖onMessageReceived可能不可靠。更稳健的做法是在CustomFirebaseMessagingService的sendNotification方法中将关键的data信息存入通知的PendingIntent的extras中。当用户点击通知启动MainActivity时再从Intent中取出数据交给Unity。这保证了即使进程被杀关键信息也能通过用户交互传递回来。Unity与原生通信优化对于需要频繁通信或传递复杂数据的场景UnitySendMessage可能成为瓶颈。可以考虑使用AndroidJavaProxy在C#端实现Java接口让原生代码直接回调C#委托性能更好。但实现稍复杂需权衡。兼容性测试务必在多种Android版本尤其是API 26和以下和多种厂商ROM小米、华为、OPPO、Vivo等上进行测试。国产ROM通常有更激进的后台管理策略可能需要引导用户进行“自启动”、“关联启动”、“电池优化无限制”等特殊设置推送才能可靠送达。这部分需要编写额外的原生代码来检测和引导。