美文网首页
推送通知实践2019

推送通知实践2019

作者: 勇往直前888 | 来源:发表于2019-04-15 11:20 被阅读0次

    需求分析

    • 推送,大多数时候让人很反感,重要性在降低。

    • 一些特殊领域,比如收钱提醒,“支付宝到账20元”这样的消息,还是很喜人的,再加上语音播报,还是容易让人接受。这次,我们要做的就是这种场景。

    版本兼容性

    • 对于推送来说,iOS8iOS10是两个重要的分界点,可以划分出3个阶段,要适配三种,确实够繁琐的。如果有条件,还是让自家的appiOS10开始吧。省事,效果也最好。

    • 兼容性都是成本,并且也不符合苹果的发展方向,最低版本能高就尽量高一点。对于推送来说,从iOS10开始是非常有必要的,这是一个革新性的变化。

    第三方服务

    • 可以自己写,代码也不多;

    • 极光推送,这个用的人挺多的;以前也一致用。

    • 友盟推送。我们用了这个,原因是工程里已经有了友盟统计。

    相对来说,极光推送会好用一点;比如,测试消息中,"mutable-content": "1", 这个值的添加,极光推送可以,但是友盟推送做不到。
    下面的代码也是友盟封装过的函数。

    关于设备号

    • 设备号是苹果推送服务器发给我们的,在文件AppDelegate
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    }
    
    • 数据类型是(NSData *)deviceToken;这个要注意一下。一般这个推送设备号是要传给后台的,不过后台需要的是字符串,一个64位的字符串。一般采用下面的方式转换。
        // NSData => NSString;去掉分隔符与空格
        NSString *deviceTokenString = [[[[deviceToken description] stringByReplacingOccurrencesOfString: @"<"withString: @""] stringByReplacingOccurrencesOfString: @">"withString: @""] stringByReplacingOccurrencesOfString: @" "withString: @""];
    
    • 只要调用注册函数,苹果推送服务器就会返回一个(NSData *)deviceToken。对于同一台设备,反复注册,苹果返回的deviceToken都是同一个。

    • 如果将app删了重装,或者代码了调用了解除注册接口。那么下次注册,苹果返回的deviceToken就不一样了。

    推送类型

    • 广播:就是所有手机都能收到推送消息

    • 组播:这里要用到别名功能。后台推送的时候要带上别名,客户端也要设置别名。那么别名能对上的手机能收到消息,其他就收不到。

    /** 绑定一个别名至设备(含账户,和平台类型)
     @warning 添加Alias的先决条件是已经成功获取到device_token,否则失败(kUMessageErrorDependsErr)
     @param name 账户,例如email
     @param type 平台类型,参见本文件头部的`kUMessageAliasType...`,例如:kUMessageAliasTypeSina
     @param handle block返回数据,error为获取失败时的信息,responseObject为成功返回的数据
     */
    + (void)addAlias:(NSString * __nonnull)name type:(NSString * __nonnull)type response:(void (^__nonnull)(id __nullable responseObject,NSError * __nullable error))handle;
    
    • 单播:这里要用到deviceToken。后台推送的时候要指定deviceToken,那么只有对应的单个设备才能收到消息。我们这里用的就是这种。

    这次,我们用的是单播,收钱了,以用户号和设备号两个唯一标识,给指定用户推送收钱提醒消息。

    关于注册:

    • 注册的函数
    /**
     友盟推送的注册接口
    
     @param launchOptions 系统的launchOptions启动消息参数用于处理用户通过消息打开应用相关信息。
     @param entity 友盟推送的注册类如果使用默认的注册,Entity设置为nil即可。如需其他的可选择其他参数,具体的参考demo或者文档。
     @param completionHandler iOS10授权后的回调。
     */
    + (void)registerForRemoteNotificationsWithLaunchOptions:(NSDictionary * __nullable)launchOptions Entity:(UMessageRegisterEntity * __nullable)entity completionHandler:(void (^ __nullable)(BOOL granted, NSError *_Nullable error))completionHandler;
    
    • 这里有个参数(NSDictionary * __nullable)launchOptions,来自启动函数。
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        return YES;
    }
    

    一般情况下(NSDictionary * __nullable)launchOptions是空的。如果APP没开,用户收到推送消息之后,点击,可以打开APP,这个时候,推送信息就包含在这个(NSDictionary * __nullable)launchOptions中。

    • 也正是因为这个(NSDictionary * __nullable)launchOptions,所以大多数的推送处理都是直接放在AppDelegate.m中。这其实很不好,可以将具体的处理放在其他文件中,做好接口封装和参数传递就可以了。另外,注册函数也不需要程序一启动就执行,理论上可以放在任何位置。只不过不注册,就得不到deviceToken,收不到消息。

    从执行的时间来说,一个相对合理的流程是:在启动的时候马上注册,这样收到苹果返回的deviceToken就相对比较早。先存本地,等用户登录成功之后,再将这个deviceToken上传自己的后台。多次上传也没关系,反正只要不解注册,所生成的deviceToken是相同的。

    关于证书

    • 推送需要证书,需要上苹果开发者网站进行申请。

    • Xcode的能力选项中,要打开后台接收消息的能力。

    • 证书分为开发证书和发布证书两种。对于推送,推荐直接使用开发证书,调试版本和发布版本可以用这一个证书,比较方便。证书只要上传友盟后台就可以了,按照相关文档操作就可以了。

    Xcode直接给手机安装的是Debug版本,对应的后台是测试模式。如果后台切换到正式模式,需要采用Ad-Hoc的方式安装Release版本才能收到消息。

    推送开关

    • 系统的设置里面有推送开关。这个关了的话,什么推动都收不到。这是系统的,不考虑。

    • 这个开关可以做在客户端。方法也很简单,如果关,只要调用解注册函数就可以了。如果开,那么再调用一下注册函数就可以了。不过,这样做有两个问题。一是,每次解注册,重新注册之后,设备的deviceToken会发生变化。二是,解注册函数有问题,友盟的注释里写清楚了:

    /** 解除RemoteNotification的注册(关闭消息推送,实际调用:[[UIApplication sharedApplication] unregisterForRemoteNotifications])
     
     iOS10.0,iOS10.1两个版本存在系统bug,调用此方法后可能会导致无法再次打开推送
     */
    + (void)unregisterForRemoteNotifications;
    
    • 这个开关可以做在服务端,如果关的话,服务器不推送消息就可以。客户端始终保持可接收推送消息的状态。我们这次就是采用这种方案。

    消息接收

    • iOS10如果指定了代理UNUserNotificationCenterDelegate,那么,消息的接收将在代理函数中。友盟文档中有示例代码:
    // 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]]) {
            [UMessage setAutoAlert:NO];
            //应用处于前台时的远程推送接受
            //必须加这句代码
            [UMessage didReceiveRemoteNotification:userInfo];
        } else {
            //应用处于前台时的本地推送接受
        }
        completionHandler(UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert);
    }
    
    • iOS9或者iOS10没有指定代理UNUserNotificationCenterDelegate,那么接收消息在AppDelegate.m的函数中:
    // 推送
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    }
    

    推荐的方式还是最低支持版本改为iOS10,上面的AppDelegate.m中的代理函数就需要出现了。

    • 只有APP在前台的时候,才能正常处理收到的推送消息。当APP关闭,或者处于后台或者锁屏的时候,上面接收消息的代码是不能执行的。

    APP关闭,或者处于后台或者锁屏的时候,,能够收到推送消息,但是,对应的响应代码是不会执行的。就算开了后台执行能力也没有什么用。

    点击响应

    • iOS10如果指定了代理UNUserNotificationCenterDelegate,那么,消息的接收将在代理函数中。
    // iOS10新增:处理后台点击通知的代理方法
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){
        NSDictionary * userInfo = response.notification.request.content.userInfo;
        if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
            // 应用处于后台时的远程推送接受
            // 必须加这句代码
            [UMessage didReceiveRemoteNotification:userInfo];
        } else {
            //应用处于后台时的本地推送接受
        }
    }
    
    • APP关闭,或者处于后台或者锁屏的时候,只要用户点击了消息,APP会被唤醒到前台,上面的代码一定会被执行。

    语音播报

    • 将推送过来的文字消息通过语音的方式播放出来,可以采用第三方的服务。比如,免费的有百度,这是链接

    • 系统自带的文字转语音也是有的

    #import <AVFoundation/AVFoundation.h>
    
    // 说一句话
    - (void)speakSentence:(NSString *)sentence {
        // 打开后台播报功能
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        
        // 文字转语音
        AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init];
        AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:sentence];
        utterance.rate = AVSpeechUtteranceDefaultSpeechRate;
        utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
        [synthesizer speakUtterance:utterance];
    }
    
    • 只有当APP处于前台的时候,才能进行语音播报。当APP关闭,或者处于后台或者锁屏的时候,语音播报代码不执行,就算开了后台执行语音能力也不起作用。

    第三的文字转语音,相对要好听一点;系统自带的声音比较机械,相对来说音质要差一点。

    推送扩展

    • APP关闭,或者处于后台或者锁屏的时候,也需要语音播报。那么就要用到UNNotificationServiceExtension

    • 这是一个新的target,和主工程完全隔离,相当于一个独立的APP

    • 相当于安卓的服务,一般是没有界面的。

    • APP关闭,或者处于后台或者锁屏的时候,这个UNNotificationServiceExtension一直是活跃的。所以,可以把语音播报代码放在这里。

    • 友盟的例子是在这里下载图片,实现富文本推送。我们这里是提取推送消息,进行语音播报。

    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
        
        // Modify the notification content here...
        
        // 播报声音
        NSString *body = self.bestAttemptContent.userInfo[@"aps"][@"alert"][@"body"];
        if (body != nil) {
            [self speakSentence:body];
        }
        
        // 转发通知
        self.contentHandler(self.bestAttemptContent);
    }
    
    - (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);
    }
    
    // 说一句话
    - (void)speakSentence:(NSString *)sentence {
        // 打开后台播报功能
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        
        // 文字转语音
        AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc] init];
        AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:sentence];
        utterance.rate = AVSpeechUtteranceDefaultSpeechRate;
        utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
        [synthesizer speakUtterance:utterance];
    }
    
    • 扩展和主APP是两个独立的个体,不能共享数据。如果要进行通讯,那么就要用到App-Group功能

    相关文章

      网友评论

          本文标题:推送通知实践2019

          本文链接:https://www.haomeiwen.com/subject/gaucwqtx.html