iOS小技能:訊息推送擴充套件的使用

語言: CN / TW / HK

這是我參與11月更文挑戰的第5天,活動詳情檢視:2021最後一次更文挑戰

引言

iOS15引入了訊息推送的新屬性中斷級別interruptionLevel,具體的列舉值

```objectivec typedef NS_ENUM(NSUInteger, UNNotificationInterruptionLevel) { // Added to the notification list; does not light up screen or play sound UNNotificationInterruptionLevelPassive,

// Presented immediately; Lights up screen and may play a sound
UNNotificationInterruptionLevelActive,

// Presented immediately; Lights up screen and may play a sound; May be presented during Do Not Disturb
UNNotificationInterruptionLevelTimeSensitive,

// Presented immediately; Lights up screen and plays sound; Always presented during Do Not Disturb; Bypasses mute switch; Includes default critical alert sound if no sound provided
UNNotificationInterruptionLevelCritical,

} API_AVAILABLE(macos(12.0), ios(15.0), watchos(8.0), tvos(15.0));

```

  • Passive:被動型別的通知不會使手機亮屏並且不會播放聲音。
  • Active: 活動型別的通知會使手機亮屏且會播放聲音,為預設型別。
  • Time Sensitive(時效性):會使手機亮屏且會播放聲音;可能會在免打擾模式(焦點模式)下展示。
  • Critical(關鍵):會立刻展示,亮屏,播放聲音,無效免打擾模式,並且能夠繞過靜音,如果沒有設定聲音則會使用一種預設的聲音。

因此當我們的訊息推送比較重要的時候,比如收款到賬的通知,可以利用訊息推送擴充套件來修改訊息推送的中斷級別為時效性,這樣手機接收的時候會亮屏且會播放聲音;即使在免打擾模式(焦點模式)下也會展示。

我們也可以通過Notification Service Extension修改推送sounds欄位來播報自定義的語音。

I Service Extension開發步驟

實現方式:採用Service Extension並結合本地通知進行實現。

iOS 10新增了Service Extension,這意味著在APNs到達我們的裝置之前,還會經過一層允許使用者自主設定的Extension服務進行處理,為APNs增加了多樣性。

本文就是利用Service Extension處理訊息並語言播報,來解決iOS12.1系統以上在後臺或者被殺死無法語音播報的問題

若主工程 Target 最低支援版本小於10.0,擴充套件 Target 系統版本設定為10.0。

若主工程 Target 最低支援版本大於10.0,則擴充套件 Target 系統版本與主工程 Target 版本一致。

通知的內容中 mutable-content 欄位必須為1

demo下載:https://download.csdn.net/download/u011018979/14026303

1.1 建立NotificationServiceExtension

  • 新建Notification Service Extension

在這裡插入圖片描述

注意:

1、Service Extension的Bundle Identifier不能和Main Target(也就是你自己的App Target)的Bundle Identifier相同,否則會報BundeID重複的錯誤。

2、Service Extension的Bundle Identifier需要在Main Target的名稱空間下,比如說Main Target的BundleID為io.re.xxx,那麼Service Extension的BundleID應該類似與io.re.xxx.yyy這樣的格式。

  • 建立NotificationService.m繼承UNNotificationServiceExtension ,並實現方法- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler

    Service Extension服務已經建立成功之後,你的專案中包含兩個方法。 在這裡插入圖片描述 1、didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *contentToDeliver))contentHandler APNs到來的時候會呼叫這個方法,此時你可以對推送過來的內容進行處理,然後使用contentHandler完成這次處理。但是如果時間太長了,APNs就會原樣顯示出來。 也就是說,我們可以在這個方法中處理我們的通知,個性化展示給使用者。

    2、serviceExtensionTimeWillExpire 而serviceExtensionTimeWillExpire方法,會在過期之前進行回撥,此時你可以對你的APNs訊息進行一下緊急處理。

  • NotificationService.m ```objectivec @interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent bestAttemptContent;

@end

@implementation NotificationService /** Call contentHandler with the modified notification content to deliver. If the handler is not called before the service's time expires then the unmodified notification will be delivered APNs到來的時候會呼叫這個方法,此時你可以對推送過來的內容進行處理,然後使用contentHandler完成這次處理。但是如果時間太長了,APNs就會原樣顯示出來。 也就是說,我們可以在這個方法中處理我們的通知,個性化展示給使用者。

/ - (void)didReceiveNotificationRequest:(UNNotificationRequest )request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy];

NSLog(@"NotificationService_%@: dict->%@", NSStringFromClass([self class]), self.bestAttemptContent.userInfo);

self.bestAttemptContent.sound = nil;

warning 如果系統大於12.0就走語音包合成檔案播報方法

if (yjIOS10) {
    __weak typeof(self) weakSelf = self;
    [[KNAudioTool sharedPlayer] playPushInfo:weakSelf.bestAttemptContent.userInfo backModes:YES completed:^(BOOL success) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {

            NSMutableDictionary *dict = [strongSelf.bestAttemptContent.userInfo mutableCopy] ;
                [dict setObject:[NSNumber numberWithBool:YES] forKey:@"hasHandled"] ;

            strongSelf.bestAttemptContent.userInfo = dict;


            strongSelf.contentHandler(self.bestAttemptContent);
        }
    }];
} else {
    self.contentHandler(self.bestAttemptContent);
}

} /* 而serviceExtensionTimeWillExpire方法,會在過期之前進行回撥,此時你可以對你的APNs訊息進行一下緊急處理。 / - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. self.contentHandler(self.bestAttemptContent); }

```

1.2 建立 AudioTool

iOS12.1 以下使用AVAudioPlayer進行語音播報。 iOS12.1 - iOS14 可以使用本地通知進行語音播報。 iOS15 通過修改推送sounds欄位來播報自定義的語音。

在這裡插入圖片描述 後臺或者鎖屏狀態下播放音訊檔案

AVAudio Session的Category值需要使用AVAudioSessionCategoryPlaybackAVAudioSessionCategoryPlayAndRecord ```objectivec [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:NULL]; [[AVAudioSession sharedInstance] setActive:YES error:NULL];

        [self playAudioFiles];

```

CategoryOptions根據實際需要可選擇

MixWithOthers(與其他聲音混音) DuckOthers(調低其他聲音的音量)

1.3 配置專案

  • 整合JPush

```objectivec pod 'JPush'

```

如果遇到這個問題:

```objectivec CDN: trunk URL couldn't be downloaded: https://raw.githubusercontent.com/CocoaPods/Specs/master/Specs/b/0/d/JPush/3.3.3/JPush.podspec.json Response: Couldn't connect to server

```

新增一下官方source即可

```objectivec source 'https://github.com/CocoaPods/Specs.git'

```

  • 新增 push notification 及background modes

在這裡插入圖片描述

  • 準備資原始檔Resource 在這裡插入圖片描述

1.4 註冊推送

registerJPUSH

```objectivec - (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { // Override point for customization after application launch.

if (yjIOS10) {
    //通知授權
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    center.delegate = self;
    [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (granted) {
            // 點選允許
            [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
                NSLog(@"yangjing_%@: settings->%@", NSStringFromClass([self class]),settings);
            }];
        } else {
            // 點選不允許

        }
    }];
    [[UIApplication sharedApplication] registerForRemoteNotifications];

} else {
    // iOS8-iOS10註冊遠端通知的方法
    UIUserNotificationType types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
    UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}

//初始化JPushSDK
[[JPushTool shareTool] registerJPUSH:launchOptions];



return YES;

}

```

  • 通知事件處理

```objectivec

  • (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

    [UIApplication sharedApplication].applicationIconBadgeNumber = 0; [[UIApplication sharedApplication] cancelAllLocalNotifications];

    [[JPushTool shareTool] setBadge:0]; }

  • (void)application:(UIApplication )application didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings )notificationSettings { // register to receive notifications

    [application registerForRemoteNotifications]; }

//遠端推送註冊成功 - (void)application:(UIApplication )application didRegisterForRemoteNotificationsWithDeviceToken:(NSData )deviceToken { NSLog(@"zkn%@: deviceToken->%@", NSStringFromClass([self class]), [deviceToken description]); [[JPushTool shareTool] registerForRemoteNotificationsWithDeviceToken:deviceToken]; }

//遠端推送註冊失敗 - (void)application:(UIApplication )application didFailToRegisterForRemoteNotificationsWithError:(NSError )error {

}

//ios10之前接收遠端推送 - (void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo { NSLog(@"yangjing_%@: userInfo->%@ ", NSStringFromClass([self class]), userInfo);

[[KNAudioTool sharedPlayer] playPushInfo:userInfo backModes:NO completed:nil];

}

//ios10之前接收本地推送 - (void)application:(UIApplication )app didReceiveLocalNotification:(UILocalNotification )notif { }

//ios10之後接收推送 - (void)userNotificationCenter:(UNUserNotificationCenter )center willPresentNotification:(UNNotification )notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(ios(10.0)){

NSDictionary * userInfo = notification.request.content.userInfo;

//遠端推送
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
    NSLog(@"%@: userInfo->%@ ", NSStringFromClass([self class]), userInfo);

    //未經過NotificationService處理
    if (![userInfo.allKeys containsObject:@"hasHandled"]) {
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
            [[KNAudioTool sharedPlayer] playPushInfo:userInfo backModes:NO completed:nil];
            completionHandler(UNNotificationPresentationOptionAlert);

        } else {
            completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert|UNNotificationPresentationOptionSound);

        }

    } else {
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
            completionHandler(UNNotificationPresentationOptionAlert);

        } else {
            completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert);

        }

    }

}

//遠端推送
else {
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert|UNAuthorizationOptionSound);

}

}

// iOS10及以上通知的點選事件 - (void)userNotificationCenter:(UNUserNotificationCenter )center didReceiveNotificationResponse:(UNNotificationResponse )response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)) { completionHandler(); // 系統要求執行這個方法 }

```

see also

更多內容請關注#小程式:iOS逆向,只為你呈現有價值的資訊,專注於移動端技術研究領域。