由于最近項目要做關(guān)于voip業(yè)務(wù),所以在此做個記錄:我們都知道當(dāng)應(yīng)用程序退出到后臺時,socket是會斷開連接,程序是被掛起的。我們現(xiàn)在要做的就是在這種情況下直接喚醒APP。
PushKit背景
PushKit是蘋果在ios8蘋果新引入的框架,一種新的push通知類型,被稱作voip push.該push方式旨在提供區(qū)別于普通APNs push的能力,PushKit區(qū)別與普通APNS的地方是,它不會彈出通知,而是直接喚醒你的APP,進入回調(diào),也就是說,可以在沒點擊APP啟動的情況下,就運行我們自己寫的代碼.
PushKit官方介紹:(https://developer.apple.com/documentation/pushkit?language=objc)
PushKit框架將特定類型的通知(例如VoIP邀請,watchOS復(fù)雜性更新和文件提供程序更改通知)直接發(fā)送到您的應(yīng)用程序以進行處理。
PushKit通知與您使用UserNotifications框架處理的通知不同。具體來說,PushKit通知從不顯示警報,標(biāo)記應(yīng)用程序的圖標(biāo)或播放聲音。與用戶通知相比,它們還具有以下優(yōu)勢:
- 設(shè)備僅在收到PushKit通知時才會喚醒,這可以延長電池壽命。
- 收到PushKit通知后,如果應(yīng)用程序未運行,系統(tǒng)會自動啟動它。相比之下,用戶通知無法保證啟動您的應(yīng)用。
- 系統(tǒng)會為您的應(yīng)用執(zhí)行時間(可能在后臺)處理PushKit通知。
- PushKit通知可包含比用戶通知更多的數(shù)據(jù)。
在PushKit中最主要用到的就是PKPushRegistey這個類,首先我們來分析一下這個類里面的內(nèi)容:
/* 目標(biāo)推送類型 */
PK_EXPORT PKPushType const PKPushTypeVoIP NS_AVAILABLE_IOS(8_0); //VOIP推送
PK_EXPORT PKPushType const PKPushTypeComplication NS_AVAILABLE_IOS(9_0); //Watch更新
PK_EXPORT PKPushType const PKPushTypeFileProvider NS_AVAILABLE_IOS(11_0); //文件傳輸
NS_CLASS_AVAILABLE_IOS(8_0)
@interface PKPushRegistry : NSObject
@property (readwrite,weak,nullable) id<PKPushRegistryDelegate> delegate; //代理對象
@property (readwrite,copy,nullable) NSSet<PKPushType> *desiredPushTypes;
//獲取本地緩存的Token 申請Token執(zhí)行回調(diào)后 這個方法可以直接獲取緩存
- (nullable NSData *)pushTokenForType:(PKPushType)type;
//初始化,并設(shè)置工作線程
- (instancetype)initWithQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
@end
@protocol PKPushRegistryDelegate <NSObject>
@required
//申請Token更新后回調(diào)方法
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type;
@optional
//收到推送后執(zhí)行的回調(diào)方法
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type NS_DEPRECATED_IOS(8_0, 11_0);
//同上,收到推送后執(zhí)行的回調(diào)方法,最后的block需要在邏輯處理完成后主動回調(diào)
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void(^)(void))completion NS_AVAILABLE_IOS(11_0);
//Token失效時的回調(diào)方法
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type;
@end
NS_ASSUME_NONNULL_END
PushKit證書申請
跟APNs push類似,PushKit的voip push也需要申請證書的,申請證書步驟如下:

按申請步驟操作就可以完成,相信大家都有申請證書的經(jīng)驗,在這里就不多嘴了。
生成正式之后把它下載,雙擊安裝到KeyChain中即可,這里需要在KeyChain里把VoIP證書的密鑰導(dǎo)出成.p12格式,發(fā)給你的后臺人員。(這里關(guān)于后臺具體使用的證書格式,根據(jù)情況而定,我們通常是把.p12文件經(jīng)過nodes命令生成.pem文件給后臺使用)
項目配置
和APNS一樣,需要在Project-> Capabilities里打開推送開關(guān)和配置后臺,Background Modes里把Voice over IP選項打開,同時把Background fetch ,Remote notification選項一起打開,并在Build Phases中加入PushKit.framework。

據(jù)說Xcode9的Background Modes中是沒有Voice over IP這個選項的,(我用的是Xcode10.2,還是有這個選項的)如果沒有的話,可以手動在info.plist文件中加入,在Required background modes里面添加一項 App provides Voice over IP services。
簡單代碼實現(xiàn)
在AppDelagate中需要導(dǎo)入PushKit框架#import <PushKit/PushKit.h>
注冊PushKit,注意PushKit要ios8 及以后才可以使用, 添加代理PKPushRegistryDelegate
if (CurrentSystemVersion.floatValue >= 8.0) {
PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:nil];
pushRegistry.delegate = self;
pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
}
實現(xiàn)獲取token的代理方法:
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
NSString * tokenString = [[[[credentials.token description] stringByReplacingOccurrencesOfString: @"<" withString: @""] stringByReplacingOccurrencesOfString: @">" withString: @""] stringByReplacingOccurrencesOfString: @" " withString: @“"];
}
設(shè)備從蘋果服務(wù)器獲取到了VoIP token,這個token與APNs是不一樣的。app將收到的token傳遞給push服務(wù)器。(流程和APNs類似,但是接受的代理方法和token都是不一樣的)獲取到的token也是64位的,與APNs一樣,需要去掉<>和空格。
做接收到推送的處理
// iOS 8.0~11.0
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
NSLog(@"收到push");
NSDictionary *dic=payload.dictionaryPayload[@"aps"];
UIUserNotificationType theType = [UIApplication sharedApplication].currentUserNotificationSettings.types;
if (theType == UIUserNotificationTypeNone){
UIUserNotificationSettings *userNotifySetting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:userNotifySetting];
}
UILocalNotification *backgroudMsg = [[UILocalNotification alloc] init];
backgroudMsg.timeZone = [NSTimeZone defaultTimeZone];
backgroudMsg.alertBody= [dic objectForKey:@"alert" ];
backgroudMsg.soundName = [dic objectForKey:@"sound"];
[[UIApplication sharedApplication] presentLocalNotificationNow:backgroudMsg];
}
查看PKPushRegistry.h的文件,在iOS8.0~iOS11.0是使用上面這個代理方法來實現(xiàn)喚醒后的操作,但在iOS11.0之后,系統(tǒng)也提供了另外一個方法:
//iOS 11.0之后
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void(^)(void))completion
如果一切正常,就算程序殺掉進程,重啟,退到后臺,服務(wù)器推送過來的消息都會走這個代理方法,在這里為了能看到推送的內(nèi)容,這里我是把接收到的內(nèi)容生成一個本地的推送發(fā)出來了,并且播放聲音,實際上在這個方法里面應(yīng)該處理喚醒APP之后的操作。
推送測試
在這里我在做推送測試的時候使用的是Easy APNs Provider這個應(yīng)用,使用起來還是很方便的。 附上下載地址
1.首先要添加所要發(fā)送的Token值,這里有三種添加方式:

2.然后并選擇之前創(chuàng)建好的voip_services.cer證書,

之后點擊“連接至”按鈕,查看是否連接成功。

4.設(shè)置發(fā)送的內(nèi)容(推送負(fù)載)
5.點擊“發(fā)送推送”。
