iOS开发新发现iOS开发iOS后台运行

iOS远程推送

2016-07-30  本文已影响186人  iYeso
Paste_Image.png

一.什么是远程通知

概念:由服务器发送消息给用户弹出消息的通知(需要联网)
远程推送服务,又称为APNs(Apple Push Notification Services)

二.为什么需要远程通知

三.远程通知的原理

iOS app大多数都是基于client/server模式开发的,client就是安装在我们设备上的app,server就是远程服务器,主要给我们的app提供数据,因为也被称为Provider。那么问题来了,当App处于Terminate状态的时候,当client与server断开的时候,client如何与server进行通信呢?是的,这时候Remote Notifications很好的解决了这个困境。苹果所提供的一套服务称之为Apple Push Notification service,就是我们所谓的APNs。

推送消息传输路径: Provider-APNs-Client App

我们的设备联网时(无论是蜂窝联网还是Wi-Fi联网)都会与苹果的APNs服务器建立一个长连接(persistent IP connection),当Provider推送一条通知的时候,这条通知并不是直接推送给了我们的设备,而是先推送到苹果的APNs服务器上面,而苹果的APNs服务器再通过与设备建立的长连接进而把通知推送到我们的设备上(参考图1-1,图1-2)。而当设备处于非联网状态的时候,APNs服务器会保留Provider所推送的最后一条通知,当设备转换为连网状态时,APNs则把其保留的最后一条通知推送给我们的设备;如果设备长时间处于非联网状态下,那么APNs服务器为其保存的最后一条通知也会丢失。Remote Notification必须要求设备连网状态下才能收到,并且太频繁的接收远程推送通知对设备的电池寿命是有一定的影响的。

Paste_Image.png Paste_Image.png 远程推送原理

#######3.1 为什么淘宝服务器不直接推消息给用户?

#######3.2 为什么苹果服务器可以推消息给用户?
所有的苹果设备,在联网状态下,都会与苹果的服务器建立长连接

#######3.3 疑惑:苹果在推送消息时,如何准确的推送给某一个用户,并且知道是哪一个APP?

当一个App注册接收远程通知时,系统会发送请求到APNs服务器,APNs服务器收到此请求会根据请求所带的key值生成一个独一无二的value值也就是所谓的deviceToken,而后APNs服务器会把此deviceToken包装成一个NSData对象发送到对应请求的App上。然后App把此deviceToken发送给我们自己的服务器,就是所谓的Provider。Provider收到deviceToken以后进行储存等相关处理,以后Provider给我们的设备推送通知的时候,必须包含此deviceToken。(参考图1-3,图1-4)

图1-3.png 图1-4.png 获取DeviceToken

四.如何做远程通知

五: 远程通知证书配置

#######5.1 .配置一个明确的APPID

Paste_Image.png

#######5.2 证书的配置
########5.2.1 : 在Certificates中配置证书

两个证书文件.png

########5.2.2配置描述文件
和真机描述文件完全一致


配置描述文件.png

六: 获取DeviceToken

6.1 在苹果的APNs服务器注册,以获取DeviceToken

通常在didFinishLaunchingWithOptions中添加如下代码进行注册

if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) {
        // 1.向用户请求可以给用户推送消息
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil];
        [application registerUserNotificationSettings:settings];

        // 2.注册远程通知(拿到用户的DeviceToken)
        [application registerForRemoteNotifications];
    } else {
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
    }
6.2 注册之后在另外一个代理方法中,拿到DeviceToken
// 注册成功回调方法,其中deviceToken即为APNs返回的token
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    [self sendProviderDeviceToken:deviceToken]; // 将此deviceToken发送给Provider
}
// 注册失败回调方法,处理失败情况
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {

}

在iOS8之后增加了可操作通知类型,可操作通知允许开发者添加自定义跳转事件。这些高级功能此篇文章不讲解,有兴趣的同学可自己去了解UIUserNotificationAction,UIMutableUserNotificationAction ,UIUserNotificationCategory ,UIMutableUserNotificationCategory这几个类。

6.3 将DeviceToken发送到服务器即可
6.4 处理接收到远程通知消息(会回调以下方法中的某一个)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //  userInfo为收到远程通知的内容
    NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
    if (userInfo) {
         // 有推送的消息,处理推送的消息
    }
    return YES;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

}

OS7之前苹果是不支持多任务的,这也是iOS系统对硬件要求低,流畅性好的原因之一。iOS7之后,苹果开始支持多任务,即App可在后台做一些更新UI、下载数据的操作等。若要接收到远程推送的时候要在后台做一些事情则需要把后台远程推送模式打开。不适配iOS7之前系统的项目建议使用此后台模式,充分利用苹果推出的多任务模式,不枉费苹果的一片苦心啊!设置后台模式方法项目对应TARGETS-Capabilities-Background Modes-Remote Notifications具体设置方法如下图(图2-1)。


图2-1

此方法不论App处于Foreground状态还是处于Background状态,收到远程推送消息的时候都会立即调用此方法。此方法需要配置后台模式并且在推送负载中必须有content-available此key值,对应的value值为1(详细介绍参考下面【远程通知负载内容】)。

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

    // 在此方法中一定要调用completionHandler这个回调,告诉系统是否处理成功

    UIBackgroundFetchResultNewData, // 成功接收到数据
    UIBackgroundFetchResultNoData,  // 没有接收到数据
    UIBackgroundFetchResultFailed   // 接受失败
    if (userInfo) {
        completionHandler(UIBackgroundFetchResultNewData);
    } else {
        completionHandler(UIBackgroundFetchResultNoData);
    }
}

可操作通知类型收到推送消息时回调方法

// 此两个回调方法对应可操作通知类型,具体使用方法参考以上方法很容易理解,不在详细叙述
- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier 
forRemoteNotification:(NSDictionary *)userInfo 
completionHandler:(void(^)())completionHandler {

}

- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier 
forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo 
completionHandler:(void(^)())completionHandler {

}

7. 测试远程通知

Paste_Image.png Paste_Image.png Paste_Image.png

八: 监听远程通知的点击

8.1 为什么要监听远程通知的点击
8.2 如何监听点击
// 和本地通知基本一致,只是这里是接收到远程通知
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    // 跳转到固定的界面
    if (application.applicationState == UIApplicationStateInactive) {
        // 进行页面的跳转
    } else {
        // 其他情况不需要跳转
    }
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // 判断是否是通过点击通知打开了应用程序
    if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
        // 跳转代码
    }

    return YES;
}

九: 指定用户的推送

对于要求用户登录的App,推送是可以指定用户的,同一条推送有些用户可以收到,但是有些用户又不能收到。说起来这个就要提到另外的一个token了,一般称之为userToken,userToken一般都是根据自己公司自定义的规则去生成的。userToken是以用户的账号加对应的密码生成的。这样结合上面提到的deviceToken,就可以做到根据不同的用户推送不同的消息。deviceToken找到对应某台设备和该设备上的应用,而userToken对应找到该用户。客户端在上报deviceToken的时候,要把userToken对应一起上报给服务端也就是Provider。

十: 激光推送的使用

10.1 激光推送的作用
Paste_Image.png
10.2 如何集成激光推送?
Paste_Image.png Paste_Image.png

十一: 利用runtime实现推送消息万能跳转

此段参考了@汉斯哈哈哈的一篇iOS 万能跳转界面方法万能跳转就是可以跳转到指定的任意一个界面,但是这个和服务端耦合性太强,使用的时候要慎重考虑,而且公司一般都是iOS,Android共用同一套推送规则很难让服务端在给你开一条新的推送规则,不便于维护,而且成本也是需要考虑的。写此段的目的就是当产品有这样的需求的时候还是可以参考一下的。

#######11.1 定义推送规则

// 客户端控制器的属性
@interface YBViewController : UIViewController
/** 频道Id */
@property (nonatomic, copy) NSString *Id;
/** 频道type */
@property (nonatomic, copy) NSString *type;
@end

// 服务端推送数据格式
{
    "aps"      :     { "alert" : "Provider push messag" },
    "class"    :     "YBViewController",
    "property" :     {
         "Id"   :   1314,
         "type" :   "customType"
    }
}

########11.2 跳转逻辑

/ 接收到推送后跳转
- (void)didReceiveRemoteNotificationAndPushToViewController:(NSDictionary *)userInfo {
    // 创建类
    NSString *class = userInfo[@"class"];
    const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
    Class newClass = objc_getClass(className);

    if (!newClass) {
        Class superClass = [NSObject class];
        newClass = objc_allocateClassPair(superClass, className, 0);
        objc_registerClassPair(newClass);
    }

    // 创建跳转控制器对象
    id destinationViewController = [[newClass alloc] init];

    // 对该对象赋值属性
    NSDictionary *propertys = userInfo[@"property"];
    [propertys enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        // 检测这个对象是否存在该属性
        if ([self checkIsExitPropertyWithdestinationViewController:destinationViewController verifyPropertyName:key]) {
            [destinationViewController setValue:obj forKey:key];
        }
    }];

    // 跳转
    UITabBarController *tabViewController = (UITabBarController *)self.window.rootViewController;
    UINavigationController *sourceViewController = (UINavigationController *)tabViewController.viewControllers[tabViewController.selectedIndex];
    [sourceViewController pushViewController:destinationViewController animated:YES];

}

// 检测对象是否存在该属性
- (BOOL)checkIsExitPropertyWithdestinationViewController:(id)destinationViewController verifyPropertyName:(NSString *)verifyPropertyName {
    // 获取对象里的属性列表
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([destinationViewController class], &outCount);

    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        // 属性名转成字符串
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        // 判断该属性是否存在
        if ([propertyName isEqualToString:verifyPropertyName]) {
            free(properties);
            return YES;
        }
    }

    free(properties);
    return NO;
}

参考:
苹果开发者文档Local and Remote Notification Programming Guide
iOS 万能跳转界面方法
iOS推送之远程推送

上一篇下一篇

猜你喜欢

热点阅读