The technology behind iOS push mobile messages

Author: allenzzhao, IEG operators Tencent Development Engineer

We use news push almost every day, but do you know how the news push in iOS is implemented? This article will introduce in detail the workflow of message push in iOS from multiple steps such as applying for push permission, to local and remote message push, and then to the processing of push messages by the App.

1 Overview

Message push is an important way for apps to deliver information to users. Regardless of whether the app is running or not, users can receive push messages as long as the user opens the notification permission. Developers can initiate local message pushes by calling the iOS system methods. For example, our most common alarm clock application, the App can directly initiate local notifications based on the locally stored alarm clock information, so even if there is no network, you can receive alarm reminders.

For remote message push, the business server sends the message content in a fixed format to the Apple Push Notitfication service (APNs), and then pushes it to the user’s device via Apple’s APNs server. For example, Tencent News can push current affairs and hot news to users. QQ mailbox can push users to receive new email reminders. Game App can notify players of new game benefits in this way. It can not only notify users of important information in time, but also prompt users to open or wake up the App through push messages to improve App usage rate. In addition to fixed push parameters such as title, content, prompt sound, and corner number, developers can also add custom parameters to the push message, so that users can go directly to the relevant news, email or welfare page when they click on the push message, providing better User experience and page exposure.


2. XCode configuration

Before using the message push related functions, we first need to prepare a certificate that supports the push function. Individual developers can refer to the TPNS document of Tencent Cloud to configure and export the push certificate in the Apple Developer Center.

In addition, you need to increase the message push permission in the Signing & Capabilities configuration of XCode's project configuration. After the operation is completed, Xcode will automatically generate or update the project's entitlements file, adding the APS Environment field as shown in the figure.

3. Apply for message push permission

Whether it is local push or remote push, you must first apply for push permission from the user before pushing, and only after the user is authorized can you receive the push message.

Apple introduced the UserNotifications framework in iOS10, encapsulated and upgraded push related functions. In addition to some basic local and remote message push functions that UIApplication could do before, it also added withdrawing or modifying push messages and custom notification UI. , Push message front display and other functions. In iOS10 and above, Apple recommends that developers use the requestAuthorizationWithOptions:completionHandler: method to apply for message push permission from users. This method needs to specify a UNAuthorizationOptions type parameter that describes the push permission, including alert (the title, text, etc. of the message) ), sound (message prompt tone), badge (the corner label displayed in the upper right corner of the App); you can also use the granted parameter in the completionHandler callback method of this method to determine whether the user has allowed authorization. The relevant code is as follows:

#import <UserNotifications/UserNotifications.h>
……
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:UNAuthorizationOptionSound|UNAuthorizationOptionAlert|UNAuthorizationOptionBadge
completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if(granted){
        //用户允许了推送权限申请
    }else{
        //用户拒绝了推送权限申请
    }
}];

In iOS9, you can directly use the registerUserNotificationSettings method of UIApplication. This method also needs to configure parameters such as sound, alert, and badge, but it does not provide a callback method for judging whether the user clicked on authorization or denied. The relevant code is as follows:

[[UIApplication sharedApplication] registerUserNotificationSettings:
 [UIUserNotificationSettings settingsForTypes:
  (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge)
                                   categories:nil]];

It should be noted that whether it is UserNotifications or UIApplication's method of applying for push permissions, the system pop-up window for applying for user authorization above will only be displayed once. iOS will record the user's authorization status for the App, and will not repeatedly apply for authorization from the user. Message push is an important function of the app, and it is also a good operation method. Therefore, many apps will check the authorization status of message push after launching. If the user refuses the message push permission, it will still remind the user with a certain frequency. , And then turn on the push permission of the App in the iOS settings center. The relevant code is as follows:

if(@available(iOS 10.0,*)){
    [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
        if (UNAuthorizationStatusDenied == settings.authorizationStatus) {
            //用户拒绝消息推送,弹窗提示引导用户去系统设置中进行授权
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"未打开推送功能" message:@"请在设备的\"设置-App-通知\"选项中,允许通知" preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action){
                [alert dismissViewControllerAnimated: YES completion: nil];
            }];
            UIAlertAction* ok = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
                [alert dismissViewControllerAnimated: YES completion: nil];
                NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
                if([[UIApplication sharedApplication] canOpenURL:url])
                {
                    NSURL*url =[NSURL URLWithString:UIApplicationOpenSettingsURLString];
                    [[UIApplication sharedApplication] openURL:url];
                }
            }];
            [alert addAction: cancel];
            [alert addAction: ok];
            [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated: YES completion: nil];
        }
    }];
}else{
    UIUserNotificationSettings *setting = [[UIApplication sharedApplication] currentUserNotificationSettings];
    if (UIUserNotificationTypeNone == setting.types) {
        //用户拒绝消息推送,处理方式同上
    }
}

4. Local push


In iOS10, the UserNotifications framework provides us with the UNMutableNotificationContent object describing the title, content, prompt sound, corner table and other content of the message push, the UNNotificationTrigger object describes the push time strategy of the message push, and the UNNotificationRequest object integrates the push content and time. Each Request object needs to be configured with an id to identify the push content, and UNUserNotificationCenter manages (including adding, deleting, querying and modifying) all Requests through this id. UNNotificationTrigger has four subcategories, namely UNTimeIntervalNotificationTrigger for controlling message push through time intervals, UNCalendarNotificationTrigger for controlling message push through date, UNLocationNotificationTrigger controlling message push through geographic location, and UNPushNotificationTrigger remote message push object. The relevant code is as follows:

//推送内容
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = @"推送标题";
content.body = @"推送内容";
content.sound = [UNNotificationSound defaultSound];//默认提示音
//日期推送,今日15:53:00推送本地消息
NSDateComponents* date = [[NSDateComponents alloc] init];
date.hour = 15;
date.minute = 53;
UNCalendarNotificationTrigger* calendarTrigger = [UNCalendarNotificationTrigger
       triggerWithDateMatchingComponents:date repeats:NO];
//倒计时推送,2s后推送本地消息
UNTimeIntervalNotificationTrigger *intervalTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:2 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest
       requestWithIdentifier:@"testId" content:content trigger:calendarTrigger];
//将推送请求添加到管理中心才会生效
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
   if (error != nil) {
       NSLog(@"%@", error.localizedDescription);
   }
}];

In iOS9, UIApplication provides two local message push methods, presentLocalNotificationNow and scheduleLocalNotification, which respectively represent immediate push and push according to a fixed date. UILocalNotification also describes the content of the message and the timing of the push. The sample code is a local message pushed after 2s. The soundName attribute is used to describe the prompt tone of the message. The user can customize the prompt tone (you need to pack the audio file into the installation package) or use the default prompt music. The repeatInterval and repeatCalendar attributes are used separately In accordance with the time difference and date to repeat the prompt operation. The relevant code is as follows:

UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:2];
notification.alertTitle = @"推送标题";
notification.alertBody = @"推送内容";
//notification.soundName = UILocalNotificationDefaultSoundName;
notification.soundName = @"mysound.wav";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];

5. Remote push

Unlike local message push that does not rely on network requests, it can directly call the iOS system method. The implementation of remote message push involves the interaction between user equipment, our own business server and Apple's APNs service. Different from the implementation of remote message push in the Android system, the App itself needs to maintain long link communication with the business server through the background service. The message push in iOS is realized by the direct interaction between the operating system and Apple’s APNs server, and the App itself does not need to maintain and communicate with the business server. Server connection. As long as the user has enabled the push permission, our business server can push notifications to the user at any time by calling the APNs service, which can provide developers and users with a safe and stable push service, save system resource consumption, and improve system fluency and Battery life.

The implementation of remote message push on the iOS client can be divided into the following processes:

  1. The user’s iphone communicates with Apple’s APNs server through the iOS system method call to obtain the device’s deviceToken, which is allocated by the APNs service to uniquely identify different apps on different devices. It can be considered as the deviceID, bundleId, and installation time. The relevant information is generated, the deviceToken of the App upgrade operation remains unchanged, and the deviceToken will change after uninstalling and reinstalling the App, restoring and reinstalling the operating system.

  2. Apple's APNs service is implemented based on deviceToken, so the device's deviceToken needs to be sent to our business server for subsequent message push. A device may have logged in to multiple users, and a user may have logged in to multiple devices. When we need to push different messages to different users, in addition to deviceToken, we also need to save the mapping relationship between the user’s openid and deviceToken . We can update the mapping relationship between openid and deviceToken after the user logs in successfully. After the user logs out, we cancel the mapping relationship and only save the deviceToken of the user's last logged-in device, avoiding one device receiving multiple repeated notifications and one user receiving it on different devices Multiple notifications, etc.

  3. When factual hot news appears in news apps, the background service can carry content such as message content and deviceToken, and initiate a message push request to Apple's APNs service. The push message is implemented asynchronously, as long as the request format and deviceToken check pass the APNs service. No error will be reported, but users may still not receive push messages due to network abnormalities or closed push permissions.

  4. The push message of the APNs service to the user's device is also asynchronous. When the user shuts down or the network fails to receive the push, APNs will reserve the last push message for each deviceToken and push it again after the network is restored.

5.1, get the device deviceToken

When the App starts, we can request the deviceToken from Apple's APNS server through the UIApplication's registerForRemoteNotifications method. If the request is successful, the didRegisterForRemoteNotificationsWithDeviceToken callback method will be executed. In order to facilitate the call of the business server, we generally convert the binary deviceToken to hexadecimal If the request fails, the didFailToRegisterForRemoteNotificationsWithError method will also be called with specific error information. The relevant code is as follows:

//调用系统方法请求deviceToken
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}
//deviceToken获取成功的回调
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    NSString *deviceTokenStr;
    NSUInteger length = deviceToken.length;
    if (![deviceToken isKindOfClass:[NSData class]] || length == 0) {
        return;
    }
    const unsigned char *bytes = (const unsigned char *)deviceToken.bytes;
    NSMutableString *hex = [NSMutableString new];
    for (NSInteger i = 0; i < deviceToken.length; i++) {
        [hex appendFormat:@"%02x", bytes[i]];
    }
    deviceTokenStr = [hex copy];
    NSLog(@"%@", deviceTokenStr);
}
//deviceToken获取失败的回调
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"error,%@",error);
}

5.2, background call APNs push

When the business side server calls the APNs service, it must first establish a secure connection to authenticate the identity of the developer. It is divided into two methods: Certificate-Based and Token-Based. The more commonly used is certificate-based. verification method. Push certificates are divided into development environment and production environment certificates, which correspond to different APNs push interfaces. The push certificates we export from Apple's developer platform or third-party platforms generally have two formats, p12 and pem, in order to facilitate the interface to call us You can use the following command to convert a file in p12 format to a pem certificate.

openssl pkcs12 -in push_dev.p12 -out push_dev.pem -nodes

The process of establishing a TLS connection based on a certificate is shown in the following figure:

  1. The provider server (Provider) initiates a request to establish a TLS connection to the APNs server.

  2. Its certificate returned by the APNs server is for verification by the business side server.

  3. The business side server provides its own push certificate for verification by the APNs server.

  4. After the APNs server verifies that the push certificate provided by the business-side server is correct, the TLS connection has been established, and then the business-side server can directly send a message push request to the APNs.

The PHP code for the simple implementation of the business party and APNs establishment request is as follows:

$deviceToken= '22124c450762170ca2ddb32a50381dd2c3026dbdb020f6dddcabefdca724fdd6';
//dev params
$devUrl = 'ssl://gateway.sandbox.push.apple.com:2195';
$devCertificate = 'push_dev.pem';
//product params
$proUrl = 'ssl://gateway.push.apple.com:2195';
$proCertificate = 'push_pro.pem';
// Change 2 : If any
$title = '标题';//消息标题
$content = '消息内容';//内容
$ctx = stream_context_create();
// Change 3 : APNS Cert File name and location.
stream_context_set_option($ctx, 'ssl', 'local_cert', $devCertificate);
// Open a connection to the APNS server
$fp = stream_socket_client($devUrl, $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
    exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
    'alert' =>array(
        'title'=>$title,
        'body'=>$content
    ),
    'sound' => 'default'
    );
//自定义内容
$body['userInfo'] = array(
    'url' => 'https://www.qq.com',
);
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
//发送多个就调用多次fwrite
//$result = fwrite($fp, $msg, strlen($msg));
echo $msg;
if (!$result)
    echo 'Message not delivered' . PHP_EOL;
else
    echo 'Message successfully delivered' . PHP_EOL;
// Close the connection to the server
fclose($fp);

After the business-side server establishes a secure connection with the APNs through the certificate, it can perform multiple consecutive message push operations, and each message push must specify the deviceToken and Payload parameters. Payload is a json object that is used to configure the display form of iOS when it receives a remote message push. The aps parameter contains Apple's preset alert, sound, badge and other parameters. The alert parameter can be a string or include title and body. The type of dictionary with other parameters; the badge parameter uses the plastic to set the number displayed in the upper right corner of the App icon, and the corner mark will not be displayed when the badge is set to 0; the sound parameter is used to set the push sound, if this parameter is not passed or an empty string is passed, it will be pushed There will be no prompt sound. When set to default, the system default prompt sound will be used. It can also be set to a specific audio file name. The audio file needs to be placed in the project's bundle directory in advance, and the duration cannot exceed 30s.

In addition to the preset parameters, we can also customize some parameters at the same level of aps. These parameters can also be of dictionary type, and then nest other parameters, such as our custom userInfo object in the sample code, but the payload of the general push message It should not be too large and should be controlled within 4K. It is recommended that only some key parameters such as id and url should be transmitted transparently. The specific content will be obtained through network request by the client when it receives the push.

{
    "aps" : {
        "alert" : {
            "title" : "Game Request",
            "subtitle" : "Five Card Draw",
            "body" : "Bob wants to play poker",
        },
        "badge" : 9,
        "sound" : "gameMusic.wav",
    },
    "gameID" : "12345678"
}

The above payload contains preset parameters such as the title, subtitle, content, message prompt sound, App corner number of common push messages, and a gameID parameter customized by the developer. After the user clicks on the push message, the App will automatically start or wake up from the background. We can get the custom parameters in the system's callback method, and automatically open the game page for the user according to the gameID.

5.3, message push debugging tool

When debugging the APNs interface, we can use some excellent push debugging tools to help us verify the legality of payloads or certificates. This article introduces two popular open source software, namely foreign Knuff and smartPush maintained by domestic developers.

  • Knuff : https://github.com/KnuffApp/Knuff

  • SmartPush:https://github.com/shaojiankui/SmartPush

6. App push message processing

In iOS10, the UserNotifications framework provides developers with the UNUserNotificationCenterDelegate protocol. Developers can implement the methods in the protocol to process some business logic when the App receives the push message and the user clicks on the push message. Whether it is a local push message or a remote push message, the running status of the App may be in the following three states:

  1. The App is running in the foreground. At this time, the user is using the App. When receiving a push message, the message prompt box will not pop up by default. The willPresentNotification callback method will be called. The developer can obtain the payload content of the push message from the UNNotification object, and then obtain it from Define the parameters, and then display a custom pop-up window to remind the user that a new message has been received; you can also call the completionHandler function in the willPresentNotification method to make the push message directly displayed in the foreground. When the user clicks the push message displayed in the foreground, the didReceiveNotificationResponse callback method is also Will be executed.

  2. The app runs in the background. At this time, the user clicks the push message to wake the app from the background, and the didReceiveNotificationResponse callback method will be executed. Developers can get the payload in this method, parse the custom parameters and automatically open the corresponding page.

  3. The app has not been launched. At this time, the user clicks on the push message to open the app. The developer can obtain the custom parameters in the local or remote push message from launchOptions, and jump to the relevant page after the page is initialized.

#import <UserNotifications/UserNotifications.h>
@interface AppDelegate ()<UNUserNotificationCenterDelegate>
@end

@implementation AppDelegate
//在App启动后就将AppDelegate对象配置为NotificationCenter的delegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    // NSDictionary *localNotification = [launchOptions valueForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    NSDictionary *remoteNotification = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if(remoteNotification){
        //app已退出,点击拉起了app
        NSDictionary *params = userInfo[@"userInfo"];
        //此时NavigationController还未初始化,可以先暂存参数,稍后跳转
        [PageSwitch handlePushSwitch:params];
    }
}
//用户点击推送消息的回调
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){
    UNNotification *noti = ((UNNotificationResponse *)response).notification;
    NSDictionary *userInfo = noti.request.content.userInfo;
    NSDictionary *params = userInfo[@"userInfo"];
    //根据消息推送中的参数,在用户点击推送时自动进行跳转
    [PageSwitch handlePushSwitch:params];
}
//App在前台运行时收到推送消息的回调
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(nonnull UNNotification *)notification withCompletionHandler:(nonnull void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(ios(10.0)){
    //可以让App在前台运行时也能收到推送消息
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert);
}

In iOS9, UIApplication provides the following three message push processing methods, which are callback processing methods for remote message push, remote silent push, and local message push. The first two callback methods can be used to process App remote message push. When used at the same time, only the remote silent push method will be called. When the payload contains the parameter content-available=1, the push is silent push, and silent push will not be displayed. For any push message, when the App is suspended in the background, the silent push callback method will be executed. The developer has 30s to process some business logic in the callback method, and call the fetchCompletionHandler after the processing is completed.

//远程消息推送回调方法,ios(3.0, 10.0)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
//远程静默推送回调方法,ios(7.0, *)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
    fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler API_AVAILABLE(ios(7.0));
//本地消息推送回调方法,ios(4.0, 10.0)
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;

The three methods in UIApplication are: ①When the App is running in the foreground, receiving notifications, ②When the App is running in the background, the user clicks the push message to pull up the App. Both scenarios will be called. The difference is that the first two methods correspond to remote message push The received and clicked trigger response, didReceiveLocalNotification is used for local message push. We can use the applicationState property of UIApplication to determine whether the App is running in the foreground, and then implement them separately: ①The user clicks on the message to wake up the background App and open the corresponding page, ②The user displays a custom pop-up window when the user uses the App in the foreground.

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
    if([UIApplication sharedApplication].applicationState == UIApplicationStateActive){
        NSLog(@"在前台,%@",userInfo);
    }else{
        NSLog(@"从后台进入前台,%@",userInfo);
        NSDictionary *params = userInfo[@"userInfo"];
        if([Tools isValidString:params[@"url"]]){
            NSString *routeUrl = params[@"url"];
            [PageSwitch handlePushSwitch:params];
        }
    }
}

7. Summary

This article first introduces the project configuration related to message push and the application of push permission, then introduces the different usage scenarios and implementation methods of local and remote message push respectively, and finally introduces the related callback methods and processing logic of the App after receiving the push message . In actual project development, we often choose more mature third-party messaging platforms such as Tencent Cloud Push or Jiguang Push. These platforms provide relatively complete push and data statistics services, and shield the underlying logic through interfaces and SDKs. Realization, the understanding of the implementation process of iOS message push can also help us make better use of these platforms.

Due to time constraints, my own research is not in-depth. If there are omissions or errors, please leave a message to correct me~

8. Extended reading

  1. Apple official technical documentation, https://developer.apple.com/documentation/usernotifications

  2. The most comprehensive iOS Push technology in history, https://cloud.tencent.com/developer/article/1198303

  3. iOS remote push-APNs detailed explanation, https://juejin.im/post/6844903893592178696

  4. iOS silent push advanced knowledge, https://www.jianshu.com/p/c211bd295d58

  5. iOS10 custom notification UI, https://www.jianshu.com/p/85ac47bdf387

  6. Carrier Pigeon Documentation-Introduction to Push Service, https://xg.qq.com/docs/ios_access/ios_push_introduction.html

  7. Talking about the principle and difference between iOS and Android background real-time message push, https://cloud.tencent.com/developer/article/1150967

  8. Talking about pushing messages to APNs based on HTTP2, http://www.linkedkeeper.com/167.html

  9. PHP implementation of ios push based on socket, https://www.fzb.me/2015-9-7-sockect-implement-for-apns.html

  10. How to build a highly available mobile messaging platform? , Https://www.infoq.cn/article/HA-mobile-message-push-platform

Guess you like

Origin blog.csdn.net/Tencent_TEG/article/details/109302050
Recommended