iOS推送-Category动作行为配置动态化

Posted by fengs on February 20, 2019

年前进行了一段时间的推送 iOS SDK 开发。现在在这里把 Category 动作行为配置动态化的开发思路记录一下。

众所周知,推送动作行为配置是通过(category-action)形式写入程序中,应用按需定制。现借助通知扩展程序实现一定程度的动作行为配置动态化。

一、知识点

  • iOS 8.0 以后 APNs(Apple Push Notification Service)添加了动作行为
  • iOS 10.0后添加了通知扩展程序,服务扩展(Notification Service Extension)和内容扩展(Notification Content Extension)
  • 通知服务扩展在收到远程通知后,展示之前可以对通知内容进行修改,处理时间不能超过30秒。在这里我们可以将将行为配置(Category)写入到应用程序中,进入主程序后系统也会保留此行为配置
  • 若要在通知扩展程序中响应设置的行为配置需在内容扩展工程配置中预留 Category

内容扩展配置 Category.png

二、开发思路

iOS 10 及以上

  • 服务端(APNs)发送一条带有动态动作行为配置的推送消息(mutable-content = 1),进入通知服务扩展程序(Notification Service Extension)
  • 推送服务扩展程序(Notification Service Extension)将动作行为(Category)写入系统
  • 若动作行为标记(identifier)已在推送内容扩展(Notification Content Extension)工程配置(Info.plist)中预留,长按推送通知显示配置的按钮
  • 点击按钮,进入主程序。主程序将动态动作行为配置继续保留到配置中(重写了通知配置方法),将配置的推送跳转路由持久化,路由响应。

动态行为流程.png

SDK 解析行为动态配置格式:

{
	"aps": {
		"alert": {
			"title": "主标题",
			"subtitle": "副标题",
			"body": "内容"
		},
		"badge": 0,
		"sound": "1.caf",
		"mutable-content": 1,
		"category": "push_extension_category1"
	},
	// 用于动态行为配置
	"notification_config": [{
		// 若APNs动态行为配置中category值起始包含“push_extension”字符串SDK会将其行为持久化;其他category值为一次性行为,应用下次启动时行为配置将失效,包括以前收到消息的处理。
		"identifier": "push_extension_category1",
		"intent_identifiers": ["see"],
		// UNNotificationCategoryOptions 
		"options": 0,
		// 用于点击消息,未触发按钮所对应的路由
		"default_router": {
			"router_url": "qq://detail/:id",
			"to_controller": "DetailViewController",
			"external_url": "https://www.qq.com",
			"parameters": {
				"detail_id": "111"
			}
		},
		"actions": [{
			"identifier": "see",
			"title": "go",
			// UIUserNotificationActionBehaviorTextInput 时按钮文字
			"text_input_button_title": "send",
			// UIUserNotificationActionBehaviorTextInput 输入框placeholder文字
			"text_input_placeholder": "I care~",
			// UNNotificationActionOptions
			"options": 4,
			// UIUserNotificationActionBehaviorDefault
			"behavior": 1,
			// 用于内容扩展程序 UNNotificationContentExtensionResponseOption 样式
			"extension_option": 2,
			"router": {
				"router_url": "qq://detail/:id",
				"to_controller": "DetailViewController",
				"external_url": "https://www.qq.com/",
				"parameters": {
					"detail_id": "111",
					"title_name": "input"
				}
			}
		}]
	}]
}

主应用更新 Category 配置

+ (void)p_registerForRemoteNotificationTypes:(FSAuthorizationOptions)options categorySet:(NSMutableSet *)categorySet {
    if (@available(iOS 10, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        [center getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull originCategories) {
            NSMutableSet *tempCategories = [NSMutableSet setWithSet:categorySet];
            for (UNNotificationCategory *category in originCategories) {
                // 将含有持久化标记的 category 放入配置中
                if (category.identifier && [category.identifier hasPrefix:kFSPNotificationCategoryPersistenceKey]) {
                    [tempCategories addObject:category];
                }
            }
            [center setNotificationCategories:tempCategories];
            
            // 获取所有有效的行为配置
            NSMutableArray *categorIdentifierArray = [NSMutableArray array];
            for (UNNotificationCategory *category in tempCategories) {
                if (![FSPCommon stringIsNull:category.identifier]) {
                    [categorIdentifierArray addObject:category.identifier];
                }
            }
            // 清除无效的行为动态配置
            [[FSPushDBManager defaultManager] clearInvalidNotificationConfigWithSystemCategoryIds:categorIdentifierArray];
        }];
    } 
}

服务扩展 SDK 更新 Category 配置

+ (void)p_registerForRemoteNotificationCategories:(NSMutableSet *)categories {
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    [center getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull originCategories) {
        NSMutableSet *tempCategories = [NSMutableSet setWithSet:categories];
        NSMutableArray *categorIdentifierArray = [NSMutableArray array];
        for (UNNotificationCategory *category in categories) {
            [categorIdentifierArray addObject:category.identifier];
        }
        for (UNNotificationCategory *category in originCategories) {
            // 去重
            if (![categorIdentifierArray containsObject:category.identifier]) {
                [tempCategories addObject:category];
            }
        }
        [center setNotificationCategories:tempCategories];
    }];
}

三、不足

  • 由于推送内容扩展程序限制,动态行为标记需事前写入其配置文件中,动态化不足。
  • iPhone “通知中心”将保留距当前时间一周的通知,为保持后续路由跳转不走样,同一个动态Category行为配置变动需相隔一周。在内容扩展配置中可多预留 Category 值,如7个,一天对应一个。
  • 点击推送通知,重新进入应用,会重置动作行为配置,为保留动态写入的行为,需将推送内容扩展配置的Category预留。现是将所有起始包含“push_extension”字符串的Category始终保留在应用中

四、功能扩展

服务端

可将推送跳转路由配置、动作行为配置模块化,在创建推送时直接选择事前创建的动作行为。动作行为(category)创建时每个动作(action)包含一个路由处理以及一个默认路由处理(点击推送进入应用)

配置动态化前置

对于iOS 8 ~ iOS 10系统,无法使用推送扩展程序。可先下发动态行为配置,后发送带有动态行为配置的通知。推送SDK会解析所有带有动态行为配置(节点“notification_config”)的通知/消息,并上报消息到达,以后可借用此来实现行为配置扩展。注意需考虑应用重新安装情况,建议服务端书写行为配置状态接口;上报消息时传递行为配置更新时间用来判断客户端动态行为配置持久化情况。 动态行为配置前置.png

五、效果图

同一个 category 下发送了3次不同的推送消息,变动了按钮文字以及图片(图片加载有延迟)

push.GIF

参考资料: