描述
PushKit是苹果在iOS8时引入的一个推送组件,它和传统的推送不同,传统推送在推送时,App是没有被唤醒的,这也就导致用户只能“被动”的接受显示推送内容,这就导致苹果的推送不够灵活,所以在iOS8时,苹果引入了PushKit。但是在ios13开始,Pushkit必须和Callkit同时使用,否则则会出现崩溃的现象。 CallKit的应用因为某些原因无法在国内的App Store上架,这也就导致PushKit在国内并没有真正的用起来。
如何申请PushKit证书
它的用法和APNs类似,也分为几个步骤:
1. 在苹果后台申请一个Pushkit使用的证书,选择VoIP Services Certificate。
2. 选择你要使用的App。
3. 上传你的签名文件。
4. 生成并下载你的证书。此时下载的格式是x.509,后缀是.cer。
5. 将生成的证书双击导入到电脑,此处需要注意,因为这个证书是用刚刚上传的签名文件制作的,所以只在制作签名的电脑里存在私钥,其他电脑直接导入是没有私钥,无法使用的。 (图中可以看到证书项展开后有一个钥匙的标志,表示这个证书有私钥,可以使用)
6. 刚刚导入的证书右键导出,格式为p12。导出时需要输入密码,测试导出的证书带有私钥。
如何上传证书到环信
Voip证书本身不区分开发环境与生产环境,但是因为不同环境的苹果服务器地址不同,证书需要在开发环境和生产环境分别上传,使用同一个证书。
上传操作和APNs证书基本一样,不同的地方在于上传voip证书时,bundleId需要在应用的bundleId后添加.voip,如应用bundleId为com.easemob.xxx,上传voip证书时填写com.eaasemob.xxx.voip 可以参考文档:上传证书到环信
这里需要单独说明,环信是支持上传多个推送证书的,也就是说它和您APNs的证书不冲突,您可以在您的Appkey下上传多个证书。后面会讲如何区分。
客户端配置
前提条件
1. 环信ios sdk需要使用3.7.1及以上版本; 2. 因为苹果强制pushkit需要和callkit一起使用,所以需要在ios10以上版本;
配置pushkit
在EMOptions中提供了设置PushKit证书名的Api,您需要将您在上传证书时设置的证书名称写在此处。
/*!
* \~chinese
* iOS特有属性,PushKit证书名称
*
* 只能在[EMClient initializeSDKWithOptions:]时设置,不能在程序运行过程中动态修改
*
* \~english
* Certificate name of Apple PushKit Service
*
* Can only be set when initializing the SDK with [EMClient initializeSDKWithOptions:], can't be altered in runtime.
*/
@property (nonatomic, copy) NSString *pushKitCertName;
2. 需要开启Push Notification,并且在background mode中设置voip
3. 向系统注册Pushkit token 并监听<PKPushRegistryDelegate>回调,得到Pushkit token后调用环信接口,将PushKit token传给环信sdk
PKPushRegistry *pushKit = [[PKPushRegistry alloc] initWithQueue:nil];
pushKit.delegate = self;
pushKit.desiredPushTypes = [NSSet setWithObjects:PKPushTypeVoIP, nil];
回调:
#pragma mark - PKPushRegistryDelegate
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials
forType:(PKPushType)type
{
// 将收到的pushkit token传给环信
[EMClient.sharedClient registerPushKitToken:pushCredentials.token completion:nil];
}
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
NSLog(@"获取pushkit token 失败");
}
// 收到pushkit推送时的回调方法
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion
{
}
到此,注册的操作就已经完成。
配置CallKit
iOS13后,苹果为了防止Voip推送被滥用,规定必须集成CallKit才能使用,否则会造成crash。CallKit主要用于APP在收到PushKit推送时,将状态信息通知系统,并将用户的接听、拒绝、挂断等操作回传给APP。
CallKit有两个主要的类CXProvider和CXCallController CXProvider可以将一些外来事件通知给系统 CXCallController可以让系统收到App的一些Request,用户的action,内部的事件
App收到PushKit推送时,首先创建展示UI
_providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"环信"];
_providerConfiguration.supportsVideo = YES;
_providerConfiguration.maximumCallsPerCallGroup = 1;
_providerConfiguration.maximumCallGroups = 1;
// CXHandleTypePhoneNumber, CXHandleTypeGeneric, CXHandleTypeEmailAddress
_providerConfiguration.supportedHandleTypes = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:CXHandleTypeGeneric], nil];
UIImage* iconMaskImage = [UIImage imageNamed:@"AppIcon"];
_providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(iconMaskImage);
self.provider = [[CXProvider alloc] initWithConfiguration:self.providerConfiguration];
[self.provider setDelegate:self queue:dispatch_get_main_queue()];
_callController = [[CXCallController alloc] initWithQueue:dispatch_get_main_queue()];
然后展示来电页面
[self.provider reportNewIncomingCallWithUUID:self.currentCall
update:update
completion:^(NSError * _Nullable error) {
}];
当用户在来电页面进行接听、拒绝或挂断等操作时,系统通过CXProvider的Delegate协议方法告知APP
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action
{
[action fulfill];
}
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
{
publicIsAnswer = YES;
_answerAction = action;
}
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action
{
[action fulfill];
}
发送使用pushkit的消息
目前是通过您在发消息时设置不同的扩展来实现的,格式:
{
"em_push_ext":{
"type":"call",
"custom":{
"xxx":"xxx",
"yyy":"yyy"
}
}
}
其中custom里的内容可以自己定义。
示例
+ (void)sendPushKitCallMessageToUser:(NSString *)aUsername
myNickname:(NSString *)aNickname
{
NSString *currentUsername = EMClient.sharedClient.currentUsername;
NSString *nickName = aNickname;
if (nickName && nickName.length > 0) { // 如果传过来的nickname是空,则使用当前登录的环信id
nickName = currentUsername;
}
// 构造消息并发送
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:[nickName stringByAppendingString:@"邀请您进行语音通话"]];
EMMessage *msg = [[EMMessage alloc] initWithConversationID:aUsername
from:currentUsername
to:aUsername
body:body
ext:nil];
/**
ext中,关键字和格式固定,需要为如下格式,其中type必须为call
{
@{@"em_push_ext" : @{
@"type":@"call",
@"custom":@{
@"xxx":@"xxxx"
}
}
};
}
*/
msg.ext = ({
@{@"em_push_ext" : @{
@"type":@"call",
@"custom":@{
@"nickname": nickName ?: @"",
@"caller":currentUsername,
@"action":@"call",
}
}
};
});
[EMClient.sharedClient.chatManager sendMessage:msg progress:nil completion:nil];
}
如何接收
当您完成配置pushkit后,您的pushkit token和证书名就已经在环信后台配置,此时如果您不在线,别人又发送了上面配置过ext的消息,您将收到一个pushkit推送,并收到回调。 示例如下:
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion
{
NSDictionary *pushInfo = payload.dictionaryPayload;
/*
e中的内容为发送方填入ext:custom中的内容
{
"m": "772299818058910052",
"t": "du001",
"aps": {
"badge": 2,
"alert": {
"body": "du002邀请您进行语音通话"
},
"sound": "default"
},
"e": {
"xxx": "xxx",
"yyy": "yyy"
},
"f": "du002"
}
*/
}
e字段中json对应的是在发送时设置的custom中的字段。其他字段的含义,可以参考文档推送字段解析
环信如何区分多个证书
因为你传了两个证书,每个证书都有自己的名字,当您发送消息时,ext中设置的type:call,就是告诉服务器这条消息要走pushkit,此时对方不在线,环信服务器就会优先去查看接收方是否设置了puskit的证书,如果有证书,同时又有pushkit用的token,服务器就会根据这个信息向苹果pushkit的接口发请求,并带上消息和ext中其他的信息。如果消息的ext中没有带call或者相关的字段,则只会发送使用APNs的证书和token去发APNs。
注意 因为苹果强制要求pushkit和callkit一起使用,否则会导致pushkit无法使用,针对这个情况,我们提供了一个Callkit demo供您参考,另外,当您想使用pushkit的时候,请一定要确认您的app是否需要上架和上架地区是哪里。如果您是想在国内上架,建议您使用自定义铃声或者使用Notification Service Extension的方式来达到唤醒,如果您使用这种方式,需要向ext中添加“em_push_mutable_content”字段,具体可以参考文档向apns中添加扩展字段。