--- title: "推送" slug: "game-gamepot3-push-notifications" updated: 2026-09-17T09:23:32Z published: 2026-09-17T09:23:32Z canonical: "guide.ncloud-docs.com/game-gamepot3-push-notifications" stale: true --- > ## Documentation Index > Fetch the complete documentation index at: https://guide.ncloud-docs.com/llms.txt > Use this file to discover all available pages before exploring further. # 推送 可在Classic/VPC环境下使用。 该功能可使开发人员高效地将消息从服务器或云端发送到移动设备。使用推送功能可以向移动应用和网页应用发送通知消息。推送功能支持iOS、Android应用程序等多个平台。 ## 设置仪表盘 若要使用推送服务,首先需要要准备各平台的密钥,然后在仪表盘中进行设置。 ### FCM推送密钥创建方法 如需发放FCM推送密钥,请按以下步骤按顺序操作。 1. 点击Firebase Console > **项目设置** > **云推送** > **服务账户管理**。 ![ko-gamepot3-push01.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push01.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 2. Google Cloud > **服务账户** > 选择创建的邮件账户。 ![ko-gamepot3-push02.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push02.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 3. 点击**密钥** > **添加密钥** > **新建密钥** > 选择“JSON”作为密钥类型,然后点击 **[创建]** 按钮。 - 如果存在已创建的密钥,也可使用相应文件。 ![ko-gamepot3-push03.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push03.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) ### APNS认证密钥创建方法 发送iOS推送通知需要APNs认证密钥,请按照以下步骤发放。 1. [登录Apple开发人员网站](https://developer.apple.com/) > Certificates, IDs & Profiles > 选择密钥 > 点击 **[+]** 按钮新建密钥。 - 最多可创建2个推送认证密钥。 ![ko-gamepot3-push04.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push04.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 2. 输入密钥名称,并激活Apple Push notifications service(APNs)。 ![ko-gamepot3-push05.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push05.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 3. 点击 **[Register]** 按钮发放认证密钥。 ![ko-gamepot3-push06.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push06.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 4. 查看密钥ID并下载。 - 下载的文件的扩展名为.p8。 - 密钥文件仅可下载一次。 ![ko-gamepot3-push07.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot3-push07.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) ### 仪表盘密钥设置方法 获得的密钥需要在GAMEPOT仪表板中注册,才能正常发送推送。 - 项目设置 - 关联 - Google Android(FCM)Configuration - Sender ID:Firebase控制台 > 项目设置 > 云推送 > 发送人ID - 私钥(json文件):使用记事本或文本编辑器等程序打开根据FCM推送密钥创建方法创建的JSON文件后,复制全部内容并注册。 - 项目设置 - 关联 - Apple iOS(APNs)Configuration - 证书:使用记事本或文本编辑器等程序打开根据认证密钥创建方法创建的.p8文件后,复制全部内容并注册。 - Key ID:注册根据APNS认证密钥创建方法创建的.p8文件的Key ID。 - Team ID:输入Apple开发人员网站“会员详情”中的Team ID。 - Bundle ID:输入应用包的Bundle Identifier值。 ## 推送设置方法 需在各平台完成SDK设置,才能正常接收推送通知。 ### AOS Native 如需使用GAMEPOT Android SDK,请按照以下步骤配置推送集成。 #### FirebaseMessaging集成 若要接收Android推送,需要集成FCM(Firebase Cloud Messaging)。若要集成FCM,请按照以下步骤操作。 1. **Firebase项目创建及应用注册** - 在[Firebase控制台](https://console.firebase.google.com)创建新项目。 - 在项目中添加Android应用,并输入包名。 - 下载`google-services.json`文件,并将其添加到应用模块的根目录。 2. **build.gradle设置** ``` plugins { id("com.google.gms.google-services") } dependencies { implementation("com.google.firebase:firebase-messaging-ktx:23.2.1") } ``` ``` plugins { id("com.google.gms.google-services") version "4.4.1" apply false } ``` - 在模块`app`的`build.gradle.kts`中添加下方插件和依赖项。 - Kotlin: - 在项目根的`build.gradle.kts`中添加下方插件。 - Kotlin: 3. **FCM重置及Token管理** - 运行应用时,FCM会自动初始化,并发放设备Token。 - Token注册到服务器后,用于发送推送消息。 4. **解决问题** - 如出现`Default FirebaseApp is not initialized`错误,请再次确认gradle设置。 - 如`google-services.json`文件不存在,请在Firebase控制台重新下载后添加到正确位置。 #### 设置推送通知图标 可设置接收推送消息时要显示于通知栏的小图标。如果不另行设置,则使用包含在SDK的默认图片,也可自行设置适合游戏的图标。 若使用[Android Asset Studio](http://romannurik.github.io/AndroidAssetStudio/icons-notification.html#sourcetypeclipartsourceclipartacunitsourcespacetrim1sourcespacepad0nameicstatgamepotsmall)制作,将自动按文件夹创建图像,非常方便。 设置推送通知图标的方法如下。 1. 在以下路径下分别创建`res/drawable`文件夹后,添加对应尺寸的图标图片文件。 - 下表介绍了按分辨率划分的文件夹及推荐图标尺寸。 | 文件夹名 | 长度 | | --- | --- | | res/drawable-mdpi/ | 24x24 | | res/drawable-hdpi/ | 36x36 | | res/drawable-xhdpi/ | 48x48 | | res/drawable-xxhdpi/ | 72x72 | | res/drawable-xxxhdpi/ | 96x96 | 1. 将图片文件名变更为`ic_stat_nbase_small`。 ### iOS Native 如需使用GAMEPOT iOS SDK,请按照以下步骤配置推送集成。 #### 重置推送功能 若要启用推送功能,请通过AppDelegate配置通知权限请求和Token注册逻辑。 Swift和Objective-C的AppDelegate设置示例分别如下所示。 - Swift: ``` import NBase func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { registerForRemoteNotifications() return true } func registerForRemoteNotifications() { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } else { print("The push notification permission has been denied") } } } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() NBase.setPushToken(token: token) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { NBase.setPushToken(token: "") } ``` - Objective-C: ``` #import "[ProjectName]-Swift.h" - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (granted) { dispatch_async(dispatch_get_main_queue(), ^{ [[UIApplication sharedApplication] registerForRemoteNotifications]; }); } else { NSLog(@"Push permission denied"); } }]; return YES; } - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSString *token = [self stringWithDeviceToken:deviceToken]; [NBaseBridge.shared setPushToken:token]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { NSLog(@"Failed to register for remote notifications: %@", error); } - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { completionHandler(UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionBadge); } - (NSString *)stringWithDeviceToken:(NSData *)deviceToken { NSUInteger length = deviceToken.length; if (length == 0) { return nil; } const unsigned char *buffer = deviceToken.bytes; NSMutableString *hexString = [NSMutableString stringWithCapacity:(length * 2)]; for (int i = 0; i < length; ++i) { [hexString appendFormat:@"%02x", buffer[i]]; } return [hexString copy]; } ``` #### 图片推送 若要在iOS应用中接收并处理通知图片,须添加通知服务扩展程序。 - Swift: 1. 在Xcode项目的TARGETS中添加Notification Service Extension。 2. 对已创建的Notification Service Extension模块的`NotificationService.swift`文件作出如下修改。 ``` import UserNotifications class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) guard let bestAttemptContent = bestAttemptContent, let userInfo = request.content.userInfo as? [String: Any] else { contentHandler(request.content) return } let imageURL = userInfo["imageUrl"] as? String ?? userInfo["gcm.notification.image"] as? String guard let imageURL = imageURL, !imageURL.isEmpty else { contentHandler(bestAttemptContent) return } downloadAndAttachImage(urlString: imageURL) { attachment in if let attachment = attachment { bestAttemptContent.attachments = [attachment] } DispatchQueue.main.async { contentHandler(bestAttemptContent) } } } private func downloadAndAttachImage(urlString: String, completionHandler: @escaping (UNNotificationAttachment?) -> Void) { guard let url = URL(string: urlString.trimmingCharacters(in: .whitespaces)) else { completionHandler(nil) return } let session = URLSession(configuration: .ephemeral) let task = session.downloadTask(with: url) { (location, response, error) in if let error = error { completionHandler(nil) return } guard let location = location else { completionHandler(nil) return } let fileManager = FileManager.default let tmpDirectory = NSTemporaryDirectory() let tmpFile = "image_\(Date().timeIntervalSince1970).png" let tmpPath = (tmpDirectory as NSString).appendingPathComponent(tmpFile) let tmpURL = URL(fileURLWithPath: tmpPath) do { if fileManager.fileExists(atPath: tmpPath) { try fileManager.removeItem(atPath: tmpPath) } try fileManager.moveItem(at: location, to: tmpURL) let attachment = try UNNotificationAttachment( identifier: UUID().uuidString, url: tmpURL, options: [UNNotificationAttachmentOptionsTypeHintKey: "public.png"] ) completionHandler(attachment) } catch { completionHandler(nil) } } task.resume() DispatchQueue.main.asyncAfter(deadline: .now() + 25) { task.cancel() } } override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } } ``` - Objective-C: 1. 在Xcode项目的TARGETS中添加Notification Service Extension。 2. 对已创建的Notification Service Extension模块的`NotificationService.m`文件作出如下修改。 ``` #import "NotificationService.h" - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; NSDictionary *userInfo = request.content.userInfo; NSString *imageUrl = userInfo[@"imageUrl"]; if (!imageUrl) { imageUrl = userInfo[@"gcm.notification.image"]; } if (imageUrl && imageUrl.length > 0) { [self downloadAndAttachImage:imageUrl completionHandler:^(UNNotificationAttachment *attachment) { if (attachment) { self.bestAttemptContent.attachments = @[attachment]; } self.contentHandler(self.bestAttemptContent); }]; } else { self.contentHandler(self.bestAttemptContent); } } - (void)downloadAndAttachImage:(NSString *)urlString completionHandler:(void(^)(UNNotificationAttachment *attachment))completionHandler { NSURL *url = [NSURL URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]]; if (!url) { completionHandler(nil); return; } NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; [[session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if (error) { completionHandler(nil); return; } if (!location) { completionHandler(nil); return; } NSString *tmpDirectory = NSTemporaryDirectory(); NSString *tmpFile = [NSString stringWithFormat:@"image_%f.png", [[NSDate date] timeIntervalSince1970]]; NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; NSURL *tmpURL = [NSURL fileURLWithPath:tmpPath]; NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:tmpPath]) { [fileManager removeItemAtPath:tmpPath error:nil]; } NSError *moveError = nil; [fileManager moveItemAtURL:location toURL:tmpURL error:&moveError]; if (moveError) { completionHandler(nil); return; } NSError *attachmentError = nil; UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:[[NSUUID UUID] UUIDString] URL:tmpURL options:@{UNNotificationAttachmentOptionsTypeHintKey: @"public.png"} error:&attachmentError]; if (attachmentError) { completionHandler(nil); return; } completionHandler(attachment); }] resume]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [session invalidateAndCancel]; }); } - (void)serviceExtensionTimeWillExpire { if (self.contentHandler && self.bestAttemptContent) { self.contentHandler(self.bestAttemptContent); } } ``` ### Unity 在基于Unity的项目中,需在同时考虑Android和iOS构建的基础上设置配置文件和依赖项。 #### 添加firebase配置文件 若要在Unity中使用Android推送通知(Firebase Messaging),请如下准备firebase配置文件。 1. 在[Firebase控制台](https://console.firebase.google.com)中注册Android应用,并下载`google-services.json`文件。 2. 将下载得到的`google-services.json`文件复制到Unity项目的`./Assets`件夹中。 - 路径示例:`./Assets/google-services.json` #### 添加Unity FCM SDK 若要在Unity中集成FCM SDK,应按以下方式添加库。 1. 在`/Assets/NBaseSDK/Editor/NBaseSDKDependencies.xml`路径中添加`firebase-messaging-ktx`库。 - XML: ``` ``` 1. 点击`Assets > External Dependency Manager > Android Resolver > Force Resolve`,强制应用库依赖相。 #### 设置推送通知图标(Unity) 接收Unity推送消息时,可设置要显示于通知栏的小图标。如果不另行设置,则使用包含在SDK的默认图片,也可自行设置适合游戏的图标。 若使用[Android Asset Studio](http://romannurik.github.io/AndroidAssetStudio/icons-notification.html#sourcetypeclipartsourceclipartacunitsourcespacetrim1sourcespacepad0nameicstatgamepotsmall)制作,将自动按文件夹创建图像,非常方便。 在Unity引擎202x及更高版本中,设置推送通知图标的方法如下。 1. 在以下路径下分别创建`res/drawable`文件夹后,添加对应尺寸的图标图片文件。 - 下表介绍了资源路径和分辨率。 | 文件夹名 | 长度 | | --- | --- | | /Assets/Plugins/Android/GamePotResources.androidlib/res/drawable-mdpi/ | 24x24 | | /Assets/Plugins/Android/GamePotResources.androidlib/res/drawable-hdpi/ | 36x36 | | /Assets/Plugins/Android/GamePotResources.androidlib/res/drawable-xhdpi/ | 48x48 | | /Assets/Plugins/Android/GamePotResources.androidlib/res/drawable-xxhdpi/ | 72x72 | | /Assets/Plugins/Android/GamePotResources.androidlib/res/drawable-xxxhdpi/ | 96x96 | 1. 在`mainTemplate.gradle`中添加相应库。 ``` dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) 添加implementation project('GamePotResources.androidlib') // ... } ``` 1. 将图片文件名变更为`ic_stat_nbase_small`。 ## 推送状态 请在应用内实现状态变更功能,以便用户自主管理是否同意接受推送。 ### 设置推送状态 须将推送状态设为`true`才能接收推送。用于变更推送状态的各参数含义如下。 **参数描述** - **push**:是否同意接收推送(true/false) - **night**:是否同意夜间推送(以韩国时间为准,true/false) - **ad**:是否同意广告类推送(true/false) 如果广告类推送值设为false,无论是否设置了一般/夜间推送,都不会发送推送。 - **token**:推送Token值 如要变更接收推送状态的设置,请在登录后调用下列代码。 - Kotlin: ``` val pushToken = NBase.getPushToken() val pushState = com.nbase.sdk.model.PushState( push = enable, night = night, ad = ad, token = pushToken ) NBase.setPushState(pushState) { status, e -> if (e != null) { // failed. } else { // succeeded. } } ``` - Java: ``` String pushToken = _NBase.getPushToken(); com.nbase.sdk.model.PushState pushState = new com.nbase.sdk.model.PushState( Boolean.parseBoolean(enable), Boolean.parseBoolean(night), Boolean.parseBoolean(ad), pushToken ); NBase nBase = NBase.INSTANCE; nBase.setPushState(pushState, (status, e) -> { if (e != null) { // failed. } else { // succeeded. } return null; }); ``` - Swift: ``` NBase.setPushState(enable: enable, ad: ad, night: night, token: NBase.getPushToken()) { result in switch result { case .success: // succeeded. case .failure: // failed. } } ``` - Objective-C: ``` BOOL enable = YES; BOOL night = NO; BOOL ad = YES; NSString *token = [NBaseBridge.shared getPushToken]; [NBaseBridge.shared setPushState:enable night:night ad:ad token:token :^(NSDictionary * _Nullable result, NSError * _Nullable error) { if (error) { // failed. } else { // succeeded. } }]; ``` - C#: ``` bool push = true; bool ad = true; bool night = false; string token = ""; //如果填入空值,则保留现有Token NBaseSDK.NBase.setPushState(push, ad, night, token, (pushState, error) => { if (error != null) { // failed. } else { // succeeded. } }); ``` ### 确认推送状态 如要确认用户当前是否同意推送,请参考下方示例。 - Kotlin: ``` NBase.getPushState() { state, e -> if (e != null) { // failed. } else { // succeeded. } } ``` - Java: ``` NBase nBase = NBase.INSTANCE; nBase.getPushState((state, e) -> { if (e != null) { // failed. } else { // succeeded. } return null; }); ``` - Swift: ``` NBase.getPushState() { result in switch result { case .success: // succeeded. case .failure: // failed. } } ``` - Objective-C: ``` [NBaseBridge.shared getPushState:^(NSDictionary * _Nullable result, NSError * _Nullable error) { if (error) { // failed. } else { // succeeded. } }]; ``` - C#: ``` NBaseSDK.NBase.getPushState((pushState, error) => { if (error != null) { // failed. } else { // succeeded. } }); ``` ## 推送测试方法(移动端) 若要确认推送通知是否在移动环境中正常工作,请参考下方测试步骤。 - **GAMEPOT仪表盘**:可在仪表盘 > **消息** > **推送通知**菜单中确认是否发送推送。 - **Android(Firebase控制台)测试**:在Firebase console > 选择项目 > Messaging > 活动上点击新活动按钮来发送测试消息。 ![ko-gamepot-push02.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot-push02.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) ![ko-gamepot-push03.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/ko-gamepot-push03.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 输入在应用中收集到的推送Token后,点击测试按钮,即可确认该Token是否能够发送。 - **iOS(CloudKit Console)测试**:在[CloudKit Console](https://developer.apple.com/icloud/cloudkit/) > Push Notifications菜单中新建Notifications。 ![CloudKit_Push Notifications.png](https://cdn.document360.io/6998976f-9d95-4df8-b847-d375892b92c2/Images/Documentation/CloudKit_Push%20Notifications.png?sv=2026-02-06&spr=https&st=2026-10-26T13%3A33%3A10Z&se=2026-10-26T14%3A33%3A10Z&sr=c&sp=r&sig=1vuMP7ElhxvjruYp4lJb2nI%2FonLuZuBN8AWGloEGIh0%3D) 输入Device Token和Payload后确认发送结果。 ### 解决问题 常见错误和解决方法如下。 Q. java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process {包名称}. Make sure to call FirebaseApp.initializeApp(Context) first. A. 当gradle文件中缺少推送设置时会发生此错误。请确认以下事项。 请确认项目顶部的`build.gradle.kts`。 - Gradle(项目根): ``` plugins { id("com.google.gms.google-services") version "4.4.1" apply false } ``` 确认应用模块的`build.gradle.kts`。 - Gradle(模块:app): ``` plugins { id("com.google.gms.google-services") } ``` Q. org.gradle.api.GradleException: File google-services.json is missing. The Google Services Plugin cannot function without it. A. 找不到`google-services.json`文件时会发生此错误。请确认该文件是否正确地放置在应用模块根文件中。(示例)`./project folder/app/google-services.json`