Unity 导出 Xcode 后 Firebase iOS 构建及授权失败
结论
日志中最先需要处理的是 Firebase iOS 原生组件版本混用:
Google Analytics for Firebase version (50200000)
does not match with Google App Measurement (50300000)
版本不一致会直接造成 FirebaseApp 创建失败。之后出现的 Database、Storage 和 Auth 异常,包括登录无响应、nil FIRApp 和 NullReferenceException,基本都是由此引发的连锁错误。
项目还有一个独立的初始化顺序问题:CheckDependencies 尚未完成,多个 Awake() 就开始访问 FirebaseAuth.DefaultInstance、FirebaseDatabase.DefaultInstance 和 FirebaseStorage.DefaultInstance。统一组件版本后,这种调用顺序仍可能引起偶发失败。
建议按以下顺序处理:
- 清除旧的 Firebase iOS 库和 CocoaPods 解析结果。
- 从同一版本的 Firebase Unity SDK 中重新导入全部模块。
- 重新解析 iOS 依赖,并生成干净的 Xcode 工程。
- 等
CheckAndFixDependenciesAsync()成功后,再创建 Auth、Database 和 Storage 实例。 - 在登录代码中处理异步任务的成功、失败和取消结果。
日志中的错误关系
日志中最早出现的有效错误是组件版本不一致,随后出现:
InitializationException: Firebase app creation failed.
默认 Firebase App 创建失败后,数据库收到空实例:
nil FIRApp instance passed to databaseForApp
DatabaseException: Failed to get FirebaseDatabase instance.
与此同时,Storage 在依赖检查完成前开始初始化:
InvalidOperationException:
Don't call Firebase functions before CheckDependencies has finished
登录代码接着访问了初始化失败的 Auth 对象:
NullReferenceException
at Authorization.LoginUser(...)
QR code reader 和手动输入最终都会调用同一个未正确初始化的 Firebase Auth 实例,所以两种登录方式都会失败。问题并不在 QR 扫描本身。
以下日志通常与 Firebase 登录失败的根因无关:
FMOD failed to initialize the output device
Analytics screen reporting is enabled
PlayerLoop internal function has been called recursively
BoringSSL ... Lower protocol stack error
相机触发的递归 PlayerLoop 和 FMOD 音频问题仍需单独处理,不过应先修复 Firebase 初始化。
清理并统一 Firebase 依赖
FirebaseAuth.unitypackage、FirebaseDatabase.unitypackage、FirebaseStorage.unitypackage 和 FirebaseAnalytics.unitypackage 不要混用不同发布包中的版本。项目里的 Firebase Unity 模块应全部来自同一套 SDK。
先检查 Unity 项目中的以下位置:
Assets/Firebase/
Assets/Plugins/iOS/
Assets/ExternalDependencyManager/
Assets/PlayServicesResolver/
重点排查:
- 同一个 Firebase 模块存在多个版本。
Assets/Plugins/iOS中残留手动复制的.framework、.a或.bundle。- 旧版 Play Services Resolver 和新版 External Dependency Manager 同时存在。
Podfile手动固定了部分 Firebase Pod 的版本。- 第三方 Unity 插件另外引入了
FirebaseAnalytics或GoogleAppMeasurement。
清理后,从同一个 Firebase Unity SDK 发布包重新导入项目实际需要的所有模块,不要只更新其中一个组件。
接着解析 iOS 依赖。External Dependency Manager 版本不同,菜单名称也会有所差异,常见入口是:
Assets > External Dependency Manager > iOS Resolver > Force Resolve
旧版本中可能显示为:
Assets > Play Services Resolver > iOS Resolver > Resolve
重新生成 CocoaPods 依赖
建议删除旧的 Unity iOS 导出目录,然后从 Unity 重新完整导出。这样可以防止旧的 Podfile.lock、Pods 和 Xcode Build Phases 继续保留错误版本。
如果必须继续使用现有导出目录,先确认其中只有 Unity 生成的内容,没有需要保留的手工修改,再执行:
cd /path/to/exported-ios-project
pod deintegrate
rm -rf Pods
rm -f Podfile.lock
pod install --repo-update
安装完成后打开 workspace:
open YourProject.xcworkspace
不要再打开 YourProject.xcodeproj,否则 CocoaPods 添加的依赖和构建设置可能无法生效。
还要检查 Podfile.lock,确认其中没有同时存在互不兼容的 FirebaseAnalytics 和 GoogleAppMeasurement 版本。具体版本号取决于当前使用的 Firebase Unity SDK,不能根据这份旧日志强行固定某个版本。依赖解析器需要为整套 Firebase SDK 选出兼容的版本组合。
检查 iOS Firebase 配置文件
Android 使用的 google-services.json 不适用于 iOS。需要在 Firebase Console 中单独注册 iOS App,并确认注册时填写的 Bundle Identifier 与 Unity Player Settings 和 Xcode Target 完全一致。
iOS 使用的配置文件是:
GoogleService-Info.plist
检查以下内容:
- 文件名仍为
GoogleService-Info.plist。 - 文件来自正确的 Firebase 项目和 iOS App。
BUNDLE_ID与应用实际使用的 Bundle Identifier 一致。- 最终 Xcode Target 的 Copy Bundle Resources 中包含该文件。
- 工程中不存在多个来自不同 Firebase 项目的同名配置文件。
修改文件名或手工复制 Android 配置都无法解决这个问题。
统一控制 Firebase 初始化
不要让 Authorization.Awake()、Database.Awake() 和 FileDownloader.Awake() 各自抢先创建 Firebase 默认实例。可以由一个启动组件统一完成初始化,其他功能等待初始化结束。
以下示例适用于提供 Firebase.Extensions 和 ContinueWithOnMainThread 的 Firebase Unity SDK:
using System;
using Firebase;
using Firebase.Auth;
using Firebase.Database;
using Firebase.Extensions;
using Firebase.Storage;
using UnityEngine;
public sealed class FirebaseBootstrap : MonoBehaviour
{
public static bool IsReady { get; private set; }
public static FirebaseAuth Auth { get; private set; }
public static FirebaseDatabase Database { get; private set; }
public static FirebaseStorage Storage { get; private set; }
public static event Action Ready;
private static FirebaseBootstrap instance;
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
FirebaseApp.CheckAndFixDependenciesAsync()
.ContinueWithOnMainThread(task =>
{
if (task.IsCanceled)
{
Debug.LogError("Firebase dependency check was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogException(task.Exception);
return;
}
DependencyStatus status = task.Result;
if (status != DependencyStatus.Available)
{
Debug.LogError(
"Firebase dependencies are unavailable: " + status);
return;
}
// 此时才允许访问 Firebase 默认实例。
FirebaseApp app = FirebaseApp.DefaultInstance;
Auth = FirebaseAuth.DefaultInstance;
Database = FirebaseDatabase.DefaultInstance;
Storage = FirebaseStorage.DefaultInstance;
IsReady = true;
Ready?.Invoke();
Debug.Log("Firebase initialized successfully.");
});
}
}
如果使用的旧版 SDK 没有 ContinueWithOnMainThread,应改用该版本支持的主线程调度方式。不要直接从后台线程更新 Unity 对象或 UI。
其他脚本的 Awake() 中不要再执行这些代码:
FirebaseAuth.DefaultInstance;
FirebaseDatabase.DefaultInstance;
FirebaseStorage.DefaultInstance;
只有当 FirebaseBootstrap.IsReady 为 true 时,才能使用这些实例。
修正登录代码
QR code 和手动输入可以共用一个登录入口。调用登录方法前,先检查 Firebase 是否已经初始化:
using Firebase.Extensions;
using UnityEngine;
public sealed class Authorization : MonoBehaviour
{
public void LoginUser(string email, string password)
{
if (!FirebaseBootstrap.IsReady ||
FirebaseBootstrap.Auth == null)
{
Debug.LogError(
"Firebase is not ready. Login request was not started.");
return;
}
if (string.IsNullOrWhiteSpace(email) ||
string.IsNullOrEmpty(password))
{
Debug.LogError("Email or password is empty.");
return;
}
FirebaseBootstrap.Auth
.SignInWithEmailAndPasswordAsync(email.Trim(), password)
.ContinueWithOnMainThread(task =>
{
if (task.IsCanceled)
{
Debug.LogError("Firebase sign-in was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("Firebase sign-in failed.");
if (task.Exception != null)
{
foreach (Exception exception in
task.Exception.Flatten().InnerExceptions)
{
Debug.LogException(exception);
}
}
return;
}
var user = FirebaseBootstrap.Auth.CurrentUser;
if (user == null)
{
Debug.LogError(
"Sign-in completed but CurrentUser is null.");
return;
}
Debug.Log("Firebase sign-in succeeded: " + user.UserId);
});
}
}
不同版本的 Firebase Unity SDK 可能会为 SignInWithEmailAndPasswordAsync() 提供不同的返回结果类型。上面的代码通过 CurrentUser 获取登录用户,避免依赖旧版 FirebaseUser 或新版登录结果类型。
如果用户可能在 Firebase 初始化完成前点击登录,可以先禁用登录按钮,等 FirebaseBootstrap.Ready 事件触发后再启用。也可以缓存登录请求,但需要防止重复提交。
登录仍失败时检查什么
日志中不再出现版本不匹配、Firebase app creation failed 和 CheckDependencies 异常后,再检查 Auth 本身的配置:
- 在 Firebase Console 的 Authentication 中启用实际使用的登录方式。电子邮件登录需要启用 Email/Password。
- 确认账号已经创建,并检查密码是否被 QR 解析逻辑截断或附加了换行符。
- 记录登录任务的
task.Exception,不要只等待界面变化。 - 使用 Google、Apple 或其他第三方登录时,还要按照对应提供方的要求配置 URL Scheme、回调地址和 Xcode Capability。电子邮件密码登录通常不需要这些回调配置。
- 如果登录成功但数据库读取被拒绝,应检查 Realtime Database 或 Cloud Firestore Security Rules。这属于 authorization 问题,不是 authentication 登录失败。
如何确认修复完成
正常启动时至少应满足以下条件:
- Xcode 日志不再报告
FirebaseAnalytics与GoogleAppMeasurement版本不匹配。 CheckAndFixDependenciesAsync()返回DependencyStatus.Available。- 默认
FirebaseApp只初始化一次。 - Auth、Database 和 Storage 都在依赖检查完成后创建。
- 登录异步任务能够明确进入成功、失败或取消分支,不再出现“点击后没有反应”。
Authorization.LoginUser()和Database.GetInitiatives()不再抛出NullReferenceException。
题目中的日志来自应用运行阶段,其中已经出现 applicationDidFinishLaunching() 和 applicationDidBecomeActive()。如果 Xcode 仍然存在编译或链接失败,还需要单独保留 Xcode Build Log 中的第一条红色错误。仅凭运行时日志无法判断 duplicate symbols、missing framework、签名失败等构建问题。
备注:内容仅供参考。
