13

iOS 消息推送那些事儿

 4 years ago
source link: https://mp.weixin.qq.com/s/QVvhq8iWBYucql9j2MD0xQ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

yIFzie6.jpg!web

vIve2er.jpg!web

Kind

网易游戏高级 iOS 开发工程师,负责基础组件 SDK 开发;对代码有轻微洁癖;目标是写好代码,做好吃的菜。

我是前言

关于 iOS 消息推送的文章,网上其实非常多,但为什么我们还要写这么一篇呢?又是准备从哪些角度去讲述 iOS 的消息推送?

iOS 的推送功能的散乱复杂,在于不同操作系统版本、用户授权状态、App 的不同生命周期状态以及推送的类型,这几个可变因素的组合下,造成的多种不同表象;而恰恰这些表象又得通过覆盖完整的测试得出,因此经常会出现这样一个现象,需求人员询问开发者对于某个特定情况下的具体表象时,开发者会说一句 emmm….不是很确定,我要验证下,再回复你

iOS 的消息推送分为 远程推送本地推送 ,而其中数 远程推送 使用得比较多,当然涉及到细节也多,所以文章会用比较大的篇幅来讲述 远程推送本地推送 会概述 iOS 10 之前跟之后两套不同的 API,在实现 本地推送 功能在某些场景的差异性跟局限性。

因此,本文内容主要讲述了推送功能中零散、甚至自己常容易不确定的知识点,以及简要总结苹果对于推送功能的迭代更新。关于证书配置、API 使用等其他知识点,可以参阅其他文章。

1. 远程推送

1.1 deviceToken何时可获取

在 iOS 平台上,远程通知的一个基本概要回路是这样的。

  1. 客户端向苹果索要 devicetoken

  2. 上报给服务端

  3. 服务端通知内容及 devicetoken 发给苹果 APNS 服务器

  4. 苹果 APNS 服务器把通知下发给某台设备

  5. 设备再根据用户对于这个 APP 所给予的通知权限,再决定要不要弹出来给用户看

根据这个流程,我们就能知道,所谓的 弹框用户授权 ,实际上只是仅仅影响到这个回路最后一个步骤,也就是能不能展示的问题。

而对于 devicetoken 的获取,这里还需要在区分下系统版本:

  • iOS 8+

    iOS 8 之后是不需要用户授权的(因为通知授权跟获取 devicetoken 是单独的两个系统 API);并且,只要拿到了 devicetoken,并且成功上报到服务端,那么通知就能下发到某台具体的设备上。

  • iOS 8-

    iOS 8 之前是通过 [UIApplication registerForRemoteNotificationTypes:] 接口获取 deviceToken

注意:消息 只是达到 ,能不能展示,就取决于用户授予的权限

1.2 deviceToken在iOS 8之前的特殊表象

  • 用户首次安装,首次打开 APP,允许授权:

    在 iOS8 之前,也在这里指的是 iOS 7.x 的系统 (iOS 6 及以下就忽略掉了),通知授权跟获取 devicetoken 是同一个 API。

    那么你可能就会想到,既然同一个 API,是不是用户授权之后,就同步拿到 devicetoken 了?

    错!坑点就在这,假设用户授权了,但这时候系统也没给回调让你拿到 devicetoken,得在 APP 下次启动时,才能拿到。

  • 用户首次安装,首次打开 APP,未授权:

    如果用户拒绝授权,很抱歉,更加拿不到 devicetoken,除非等到用户主动去系统设置里面对你的 APP 授予通知权限,APP 重新打开才能拿到 devicetoken

  • 测试中发现的现象:

2Uzy2mu.png!web

上面这张图是在 iOS 7 设备上,首次安装时,拒绝了授权,进入系统设置看到如上图所示。从上面的图我们得到什么信息呢?

一个很重要的点,就是 在通知中心 中显示在锁定屏幕上显示 不是影响获取 devicetoken 的关键因素。

另外测试中发现,基于上面图中作为当前状态

  • 如果用户打开 图标标记 或者 声音 ,这时候再启动 APP,就能拿到 devicetoken;但由于最上面的展示样式是选择了无,所以并不会展示出来给用户看到

  • 如果用户把样式选择了 横幅提醒 (图标标记或者声音都是未打开状态),这时候再启动 APP,也能拿到 devicetoken;这时候服务端下发通知,是能展示出来给用户看到的,但没有提示音跟图标红点数而已

  • 如果用户只开 标记声音 ,而展示样式 ,那么就会只听到声音,而没任何通知展示。

  • 如果用户全打开了,那就跟允许授权时一样的设置了

1.3 远程推送之静默推送

说到 iOS 的远程推送,静默推送就不得不讲。

静默推送是在 iOS 7 出现的,官方也叫 Background Update Notification ,也就是用于 APP 在处于 非 Kill 状态下,收到通知之后,可以预先处理一些数据用于用户展示,优化体验的。这一切都是在后台执行的,也不会展示通知,用户是无感知的。

静默推送的使用限制

但苹果为了不让开发者滥用这个功能,而导致耗费手机设备上更多的资源(CPU、电量等等),对静默推送也做了限制,但这具体的限制是由苹果的 APNS 服务器定的。

下面引用官方文档的一段话

(https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW1)

IMPORTANT

Background update notifications are not meant as a way to keep your app awake in the background beyond quick refresh operations, nor are they meant for high priority updates. APNs treats background update notifications as low priority and may throttle their delivery altogether if the total number becomes excessive. The actual limits are dynamic and can change based on conditions, but try not to send more than a few notifications per hour .

静默推送的参数控制

原则上,静默推送就是在后台用户不感知的,但根据官方文档说的,你在 aps 那个 dictionary 加入 alertsound 也是允许的,这时候是会展示出来给用户看到的。

To support a background update notification, make sure that the payload’s aps dictionary includes the content-available key with a value of 1. If there are user-visible updates that go along with the background update, you can set the alert, sound, or badge keys in the aps dictionary, as appropriate.

不过要注意,虽然加了 alertsound 会让通知展示出来,但同时也把 静默推送 变成 普通推送 ,也就失去了原本静默推送的作用,也就是上面描述的 APP 处于后台,收到通知能后台执行代码处理展示数据,提升体验

所以还是建议,如果想展示通知,就使用普通推送类型即可;

如果想使用静默推送做些事情,就不加 alert、sound、badge 等这些字段

1.3 从推送授权状态、推送类型、App生命周期状态三个维度总结表象

上面只是简单的宏观描述了下,远程推送这个功能的大体流程,但实际上,在远程推送这条链路上,会有多个可变因素导致了多种组合,最终也导致了不同的表象。

首先,先解释列举下可变因素:

  • APP 的前后台状态

  • 用户是否允许授权弹出通知

  • 推送分为普通推送以及静默推送

另外在表象上,又分为两种,一种是用户直观上,也就是通知在手机上的展示;第二种就是对于开发者来说,也就是收到系统的回调。

我们假设

:white_check_mark::代表用户能看到通知 :negative_squared_cross_mark::代表用户看不到通知

:heavy_check_mark::代表代码上通知到达时能收到系统的回调 :x::代表代码通知到达时收不到系统回调

要注意下,这里所讲的能否收到回调,是指通知到达的时候

QBVRvib.png!web

上面讲了通知达到时能否收到回调的情况,下面讲一下在用户操作情况下能否收到回调的情况;

当 APP 不在前台运行,用户在看到通知后,有意愿去启动 APP 的时候,根据 APP 当前的状态、以及用户启动的方式,能不能拿到回调表象也是不同的。

这个会影响到点击率的统计数据

A3aErmR.png!web

2. 本地推送

本地推送虽然在使用力度上远不及远程推送那么大,但对于某些 App 或者某些特定的场景功能,确是不可或缺的强有力工具。区别于安卓的本地推送(安卓依赖自身 App 的服务),而 iOS 是直接向系统提交注册,由系统帮你管理,所以即使用户把 App 关闭了,也能准确的在特定时间收到本地推送。

同样的,以 iOS 10 作为分界线,苹果提供了两套不同的 API,但我们这里不讲述 API 的使用不同;而是比对这两套 API,在实现某些场景需求上的不同及局限。

2.1 每xx秒重复推送

在 iOS 10+ 的系统上,可以使用 [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timeInterval repeats:YES] 实现,但 timeInterval 的值必须要大于等于 60(1 分钟);苹果不允许把周期设置为秒,这也确实没什么必要,更何况体验也是相当差。

而在 iOS 10- 的系统上,虽然能把周期的粒度通过 NSCalendarUnitMinute 设置为分钟,但只能是 每分钟 ,而无法满足比如 5 分钟、10 分钟这样的周期间隔。

2.2 每个月的第x周的周z重复推送

比如现在有个这样的需求,需要在每个月的第二个周五触发本地推送。

在 iOS 10+ 的系统上,可以使用 [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:dateComponents repeats:YES] 轻松实现,通过配置正确的 datecomponent 即可。

但在 iOS 10 之前的系统,也就是使用 UILocalNotification 无法达到需求。

2.3 本地推送过期时间

经由我们反复测试发现,本地推送过期时间为 15 分钟左右;

比如你设置一个下午 4 点的通知,但手机在 4 点之前关机,15 分之后开机,这时候通知就不会出现;

相反,如果在 4 点 15 分之前开机,通知就会展示。

或者说:设定 09 分推送,手动调整时间至 23 分可以收到,但 24 分就不会展示

3. iOS 推送功能演进历史

iOS 的推送功能,早在 iOS 3 就已支持,但系统已经到 13 的今天,从头开始讲述演进历史已然没多大必要,所以以下内容会从 iOS 7 开始,简要讲述从 7~13,每代系统升级时苹果对于推送功能的演进。

iOS 7

iOS 7 最大的改进,就是支持了静默推送。

application(_:didReceiveRemoteNotification:fetchCompletionHandle:)

iOS 8

  • 对通知权限请求重新设计,做了规范。iOS 8 之前, 本地推送远程推送 是区分对待的,只需要对远程推送进行获取用户授权。在 iOS 8 之后,无论本地推送还是远程推送,都需要向用户申请权限。

  • 新增 registerUserNotificationSettings(_:)方法获取 deviceToken

  • 可响应式通知:新增了三个类 UIUserNotificationSettings, UIUserNotificationCategory, UIUserNotificationAction

iOS 9

  • 新增 category action- Text Input,可以用于对消息的快捷回复

  • 升级了 APNs 的推送协议,基于 HTTP/2,同时提供了全新的 Provider API

iOS 10

iOS 10 是苹果有史以来最大的一次推送功能更新,可以说是 SDK 的一次大规模重构。引入全新的 UserNotifications 框架,所有推送相关功能接口都被放置其中,通知类型以及行为也得到进一步的统一,摆脱了以前的混乱现象。

  • 终于支持前台展示通知

  • 通知支持 titlesubtitle

    对于需要支持 title 跟 subtitle 的远程推送,相应的 payload 要做下修改:

    // iOS 10之前
    {
    "aps":{
     "alert":"Test",
     "sound":"default",
     "badge":1
     }
    }
    
    // 加入title跟subtitle
    {
    "aps":{
     "alert":{
       "title":"I am title",
       "subtitle":"I am subtitle",
       "body":"I am body"
       },
     "sound":"default",
     "badge":1
     }
    }
    
  • 支持取消某、更新或者移除一个特定的本地通知

  • Notification Extension

    iOS 10 新增了两个跟通知相关的拓展, Service ExtensionContent Extension ,前者可以让开发者有机会在收到通知内容之后,展示之前对通知内容做修改;后者可以用自定义的视图来展示通知内容。

EBvIveu.png!web

iOS 11

  • 隐藏通知内容

    为了更好的保护隐私,避免通知展示敏感隐私内容,新增了内容隐藏功能。这个功能在 iOS 10 就已支持,不过支持系统应用,比如 Message;iOS 11 将其支持了所有第三方应用,可以在 系统设置 里进行全局设置,也可以针对某个 App 进行单独设置。

    包含了三个选项:1. 始终(默认)2. 解锁时 3. 从不

iOS 12

  • 通知分组

    通知分组分为两种:1. 自动分组(根据 App bundleid) 2. 自定义分组(需要配合 thread-id 使用)

    同时用户可以在系统设置中进行设置

ryIFbuj.png!web

  • 支持动态更新通知 Action

    新增 notificationActions 属性,通过该属性,可以访问到现有的面向用户的通知 action,也可以为 contentExtension 建立全新的通知 action 数组进行替换

    @interface NSExtensionContext (UNNotificationContentExtension)
    
    @property (nonatomic, copy) NSArray <UNNotificationAction *> *notificationActions __IOS_AVAILABLE(12_0) __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __OSX_UNAVAILABLE;
    
    @end
    
  • 支持 contentExtension 中视图的交互

    在 info.plst 文件中新增一个键值对, UNNotificationExtensionUserInterfaceEnabled : YES

  • 通知管理界面

ne2aymV.jpg!web

  • 临时授权

    这个机制可以让你的 App 在一段时间内可以给用户试发通知,无需用户明确授权,但同时不会有声音提醒,只显示在通知中心

  • Critical Alerts

    可以在用户允许的情况下,忽略一些系统设定(比如免打扰、静音模式),给用户发送重要的通知,比如涉及到健康、安全的通知。

    但为了避免被滥用从而干扰用户,苹果对此类通知做了限制,要使用得向苹果申请授权。

  • 分组摘要格式设置

    通知分组后,在最下边会有一个消息摘要。默认格式是: 还有 n 个通知。

    可以通过以下两种方式进行自定义:

  1. 通过 UNNotificationCategory

  2. 通过 UNNotificationContent

iOS 13

  • deviceToken 更改

    Xcode11 打包的 App,安装在 iOS 13 以上系统的设备,获取到的 deviceToken 格式是有别于之前的。而由于之前网上广泛流传的 deviceToken 处理方式其实却在缺陷,导致这一次很多第三方 SDK 以及 App 都要做更新处理。这里给出合理的处理方式以作参考:

    const char *tokenBytes = (const char *)[deviceToken bytes];  
    NSMutableString *token = [NSMutableString string];  
    for (NSUInteger index = 0; index < deviceToken.length; index++) {  
      [token appendFormat:@"%02hhx", tokenBytes[index]]; 
    } 
    
  • APNs 的 HTTP 请求 header 新增字段 apns-push-type

    今年,苹果更新了向 APNS 发送通知请求的头部字段列表,新增了 apns-push-type;这个字段按照官方的描述,WatchOS 6+ 的系统是必传的,其他系统 macOS、iOS、tvOS、iPadOS 官方推荐也透传。

    apns-push-type 对应可以设置以下 6 种值:

    • alert

    • background

    • voip

    • complication

    • fileprovider

    • mdm

  • 如果需要 Background Push( 静默推送),apns-priority 必须设置为 5

    之前,对于这个字段,大多数情况下开发者都是设置为 10,告诉 APNS 这个通知立即派发;并且你不传的话,Apple 也是会帮你添加这个默认值 10。

    但在今年 2019 WWDC 上,Apple 表明,如果需要使用 Background Push,该字段必须设置为 5;

YRzamyU.jpg!web

  • 苹果停止对 TLS v1 的推送 API 的支持

    苹果之前都支持用户通过旧的 TLS v1 协议连接他们的推送 API,但现在苹果开发人员告知将会停止支持,并建议放弃 TLS v1

UBZZ7jy.jpg!web

写在最后

其实 iOS 上的通知功能是相当复杂且庞大,由于篇幅,这里也只是针对某些点做了讲述,冰山一角。对于文中未提及的其他功能,读者可自行通过参阅官方文档或者网上的其他优质文章进行了解。

同时通过简要概括了通知功能的演进历史,也能从中得知,Apple 对于通知这个功能是相当重视,无论是从接口设计、用户体验还是隐私考虑,都在持续的完善和优化。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK