推送

Prev Next

可在Classic/VPC环境下使用。

该功能可使开发人员高效地将消息从服务器或云端发送到移动设备。使用推送功能可以向移动应用和网页应用发送通知消息。推送功能支持iOS、Android应用程序等多个平台。

设置仪表盘

若要使用推送服务,首先需要要准备各平台的密钥,然后在仪表盘中进行设置。

FCM推送密钥创建方法

如需发放FCM推送密钥,请按以下步骤按顺序操作。

  1. 点击Firebase Console > 项目设置 > 云推送 > 服务账户管理
    ko-gamepot3-push01.png

  2. Google Cloud > 服务账户 > 选择创建的邮件账户。
    ko-gamepot3-push02.png

  3. 点击密钥 > 添加密钥 > 新建密钥 > 选择“JSON”作为密钥类型,然后点击 [创建] 按钮。

    • 如果存在已创建的密钥,也可使用相应文件。
      ko-gamepot3-push03.png

APNS认证密钥创建方法

发送iOS推送通知需要APNs认证密钥,请按照以下步骤发放。

  1. 登录Apple开发人员网站 > Certificates, IDs & Profiles > 选择密钥 > 点击 [+] 按钮新建密钥。

    • 最多可创建2个推送认证密钥。
      ko-gamepot3-push04.png
  2. 输入密钥名称,并激活Apple Push notifications service(APNs)。
    ko-gamepot3-push05.png

  3. 点击 [Register] 按钮发放认证密钥。
    ko-gamepot3-push06.png

  4. 查看密钥ID并下载。

    • 下载的文件的扩展名为.p8。
    • 密钥文件仅可下载一次。
      ko-gamepot3-push07.png

仪表盘密钥设置方法

获得的密钥需要在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控制台创建新项目。
    • 在项目中添加Android应用,并输入包名。
    • 下载google-services.json文件,并将其添加到应用模块的根目录。
  2. build.gradle设置

    • 在模块appbuild.gradle.kts中添加下方插件和依赖项。
    • Kotlin:
    plugins {
         id("com.google.gms.google-services")
    }
    dependencies {
         implementation("com.google.firebase:firebase-messaging-ktx:23.2.1")
    }
    
    • 在项目根的build.gradle.kts中添加下方插件。
    • Kotlin:
    plugins {
         id("com.google.gms.google-services") version "4.4.1" apply false
    }
    
  3. FCM重置及Token管理

    • 运行应用时,FCM会自动初始化,并发放设备Token。
    • Token注册到服务器后,用于发送推送消息。
  4. 解决问题

    • 如出现Default FirebaseApp is not initialized错误,请再次确认gradle设置。
    • google-services.json文件不存在,请在Firebase控制台重新下载后添加到正确位置。

设置推送通知图标

可设置接收推送消息时要显示于通知栏的小图标。如果不另行设置,则使用包含在SDK的默认图片,也可自行设置适合游戏的图标。

若使用Android Asset Studio制作,将自动按文件夹创建图像,非常方便。

设置推送通知图标的方法如下。

  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控制台中注册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:
<dependencies>
    <androidPackages>
        <androidPackage spec="io.nbase:nbasesdk:3.0.xx"/>
        <androidPackage spec="com.google.firebase:firebase-messaging-ktx:23.2.1" />
    </androidPackages>
    <iosPods>
    </iosPods>
</dependencies>
  1. 点击Assets > External Dependency Manager > Android Resolver > Force Resolve,强制应用库依赖相。

设置推送通知图标(Unity)

接收Unity推送消息时,可设置要显示于通知栏的小图标。如果不另行设置,则使用包含在SDK的默认图片,也可自行设置适合游戏的图标。

若使用Android Asset Studio制作,将自动按文件夹创建图像,非常方便。

在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

    ko-gamepot-push03.png

    输入在应用中收集到的推送Token后,点击测试按钮,即可确认该Token是否能够发送。

  • iOS(CloudKit Console)测试:在CloudKit Console > Push Notifications菜单中新建Notifications。
    CloudKit_Push Notifications.png

    输入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