PushKit/Callkit使用经验

news2024/11/17 0:18:50

前言:如果要求使用这两种库,请在查询资料并自己尝试后,多参考苹果官方的API文档:

PushKit:https://developer.apple.com/documentation/pushkit?language=objc

CallKit:https://developer.apple.com/documentation/callkit?language=objc

一、PushKit

一、简介

1.iOS10之后,苹果推出了CallKit框架增强的VoIP应用的体验,主要表现在3个方面:

  • 在锁屏状态下,如果有网络电话呼入,VoIP的应用可以打开系统电话应用的待接听界面。

  • VoIP的应用内发起通话,挂断电话等记录可以体现在系统电话应用的通话记录中。

  • 从系统电话应用的通话记录,通讯录或者Siri的进入VoIP的应用,发起通话。

二、使用(主要代码)

1.注册token
 PKPushRegistry *voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
    voipRegistry.delegate = self;
    voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
2.获取token,并将token上传到自己的服务器
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
    NSData *deviceToken = credentials.token;
    NSString *token = @"";
    if (@available(iOS 13.0, *)) {
        const unsigned char *dataBuffer = (const unsigned char *)deviceToken.bytes;
        NSMutableString *myToken  = [NSMutableString stringWithCapacity:(deviceToken.length * 2)];
        for (int i = 0; i < deviceToken.length; i++) {
            [myToken appendFormat:@"%02x", dataBuffer[i]];
        }
        token = (NSString *)[myToken copy];
    } else {
        NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
        NSString *myToken = [[deviceToken description] stringByTrimmingCharactersInSet:characterSet];
  token = [myToken stringByReplacingOccurrencesOfString:@" " withString:@""];
    }
    NSLog(@"didUpdatePushCredentials token = %@", token);
    [[PushNotificationManager shareInstance] setupPushKitToken:token];//保存token到自己的服务器
}
3.获取推送消息,校验并弹出系统电话界面
/// iOS8.0-iOS11.0
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
    [self didReceiveIncomingPushWithPayload:payload withCompletionHandler:^{}];
}
/// iOS11.0+
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type withCompletionHandler:(nonnull void (^)(void))completion {
    [self didReceiveIncomingPushWithPayload:payload withCompletionHandler:completion];
}
- (void)didReceiveIncomingPushWithPayload:(PKPushPayload *)payload withCompletionHandler:(nonnull void (^)(void))completion{
    //是否登陆-一般voip注册的token会绑定到用户
    if (![SecurityProvider isUserLogined]) {
        return;
    }
    //从字典payload.dictionaryPayload中获取推送来的数据,自己解析做逻辑处理,
    //以下代码可以当作参考,不用深究,基本思路是对推送数据的处理:
    //1.当前没有任何来电时,收到推送解析数据后直接展示系统电话界面
    //2.当前有来电未接听时,收到推送解析数据后,更新数据,保证接听时数据为最新的推送数据
    //3.当前有来电已接听时,1.不同用户打来的,忙线处理(告诉服务端当前用户忙线)2.同一个用户打来的直接进行通话。
    NSString *action = [payload.dictionaryPayload objectForKey:@"action"];
    if([action isEqualToString:@"5010"]){
        NSString * dataString= [payload.dictionaryPayload objectForKey:@"data"];
        NSDictionary * extraDic = [[ProviderDelegate shareInstance] dictionaryWithJsonString:dataString];
        
        NSString *hospital = [payload.dictionaryPayload objectForKey:@"hospital"];
        hospital = hospital ==nil?@"":hospital;//hospital为nil崩溃
        NSString *topic = [extraDic objectForKey:@"topic"];
        NSString *password = [extraDic objectForKey:@"password"];
        NSString *token = [extraDic objectForKey:@"token"];
        NSString *bizNo = [extraDic objectForKey:@"bizNo"];
        NSString *sessionId = [extraDic objectForKey:@"sessionId"];
        NSString *topicTime = [extraDic objectForKey:@"topicTime"];
        
        NSString *pushType = [payload.dictionaryPayload objectForKey:@"pushType"];
        if ([pushType isEqualToString:@"start"]) {
            
            //检查是否已有通话
            if ([[ProviderDelegate shareInstance] checkHadIncomingCall]) {
                NSString *bizNo_old = [[NSUserDefaults standardUserDefaults] valueForKey:@"bizNo"];
                
                //忙线
                //不同用户拨打
                if (![bizNo_old isEqualToString:bizNo]) {
                    [[ZoomViewManager shareInstance] busyVideoCallWithSessionId:sessionId bizNO:bizNo];
                    return;;
                }else{
                    //已经接通的情况-同一个医生特殊情况打多次-不再显示电话页面,直接进入zoom
                    //topicTime>topicTime_old:同一个医生多次拨打,用户收到的通知顺序按照时间来判断
                    if ([topicTime longLongValue]>[self.topicTime_old longLongValue]) {
                        if ([[ProviderDelegate shareInstance] checkHadCallActive]) {//是否正在通话
                            [[ZoomViewManager shareInstance] leaveVideoCall];
                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                                [[ZoomViewManager shareInstance] joinOnlyWithSesscionName:topic
                                                                             userName:hospital
                                                                      sessionPassword:password
                                                                                token:token
                                                                                bizNo:bizNo
                                                                            sessionId:sessionId];
                               });
                        }
                    }else{
                        return;
                    }
                    
                }
            }
            
            if ([topicTime longLongValue]>[self.topicTime_old longLongValue]) {
                [[NSUserDefaults standardUserDefaults] setValue:pushType forKey:@"pushType"];
                [[NSUserDefaults standardUserDefaults] setValue:hospital forKey:@"hospital"];
                [[NSUserDefaults standardUserDefaults] setValue:topic forKey:@"topic"];
                [[NSUserDefaults standardUserDefaults] setValue:password forKey:@"password"];
                [[NSUserDefaults standardUserDefaults] setValue:token forKey:@"token"];
                [[NSUserDefaults standardUserDefaults] setValue:bizNo forKey:@"bizNo"];
                [[NSUserDefaults standardUserDefaults] setValue:sessionId forKey:@"sessionId"];
                self.topicTime_old = topicTime;
            }
            
            //没有通话可以展示系统通话界面
            if (![[ProviderDelegate shareInstance] checkHadIncomingCall]) {
                [[ProviderDelegate shareInstance] reportIncomingCallHandle:hospital withCompletionHandler:completion];
            }
 
        }
    }
}
注意事项:

1.官网介绍此方法的实现必须通过调用应用对象的方法向CallKit框架报告类型通知。否则在 iOS 13.0 及更高版本上,如果未能向 CallKit 报告调用,系统将终止应用,不再给应用发送推送(应用在前台还可以继续收到)。

2.// iOS11.0+ withCompletionHandler:(nonnull void (^)(void))completion,其中completion必须在callkit唤起系统电话界面完成时候调用,否则会导致部分机型间断的收到消息推送(必须发两次消息推送才能收到消息)

三、踩坑经验

1.keychain问题:在杀死app/杀死app并锁屏状态,keyChain有保护机制,所以此时收到推送消息,去读取keyChain数据时会导致读取失败,影响app正常流程。

Locked home screens. The keychain tutorials always left the accessibility settings for the keychain blank,

so it would default to Apple's lowest/safest access level.

This level however doesn't allow keychain access if the user has a passcode on the lock screen. Bingo!

This explains the sporadic behavior and why this only happens to a small percentage of users.

二、CallKit

一、简介

CallKit 是一个iOS10新框架,用于改善 VoIP 的体验,允许 App 和原生的 Phone UI 紧密集成,你的 App 将能够:

  • 调用原生的呼入界面,无论锁屏/不锁屏状态。

  • 从原生电话 App 的通讯录、个人收藏、最近通话中发起通话。

  • 通过系统服务监听来电和去电。

  • 用电话通讯录识别或拦截来电。

callkit并不能直接通话/视频,需要配合语音/视频库进行通信(如:zoom),

杀死app/锁屏都会执行CallKit代码,但一些网络请求等会失败

二、使用(主要代码)

1.初始化一个CXProvider,定义成单利
- (instancetype)init {
    self = [super init];
    if (self) {
        CallManager *manager = [CallManager shareInstance];
        self.callManager = manager;
        CXProvider *provider = [[CXProvider alloc]initWithConfiguration:[self providerConfiguration]];
/*
*设置代理可以接收到系统电话界面上的所有操作回调
*例外:点击视频图标无回调,自动打开app(暂无用没深究)
*/
        [provider setDelegate:self queue:nil];
        self.provider = provider;
    }
    return self;
}
 /*
*初始定义本地电话页面显示效果
*接到消息推送,唤起电话页面前可以更新该配置CXCallUpdate
*详解见官网CXProviderConfiguration
*/
- (CXProviderConfiguration *)providerConfiguration{
    CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc]initWithLocalizedName:@"HELPO"];
    providerConfiguration.supportsVideo = YES;
    providerConfiguration.maximumCallsPerCallGroup = 1;
    providerConfiguration.maximumCallGroups = 1;
    providerConfiguration.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypePhoneNumber)];
    return providerConfiguration;
}
2.弹出系统电话界面
- (void)reportIncomingCallHandle:(NSString *)handle withCompletionHandler:(nonnull void (^)(void))completion{
    if (!self.isHadReportIncomingCall) {
        self.isHadReportIncomingCall = YES;
        NSUUID *uuid = [NSUUID UUID];
        self.uuid = uuid;
        CXCallUpdate *update = [[CXCallUpdate alloc]init];
        update.remoteHandle = [[CXHandle alloc]initWithType:CXHandleTypePhoneNumber value:handle];
        update.localizedCallerName =handle;
        update.supportsHolding = NO; //通话过程中再来电,是否支持保留并接听
        update.supportsGrouping = NO; //通话是否可以加入一个群组
        update.supportsDTMF = NO; //是否支持键盘拨号
        update.hasVideo = YES;//本次通话是否有视频

     [self.provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
            Call *call = [[Call alloc]initWith:uuid isOutGoing:NO handle:handle];
            [self.callManager addCall:call];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                for (Call *call in self.callManager.calls) {
                    if (call.callState == CallStateConnecting && call.uuid == uuid) {
                        call.isAutoEnd = YES;
                        [self stopCalling];//倒计时3分钟,用户还未接听,代码挂断
                    }
                }
            });
            completion();
        }];
    }
}
注意事项:
1.pushkit有提到,completion必须在reportNewIncomingCallWithUUID完成之后调用
3.点击接通代理回调
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action{
    Call *call = [self.callManager callWithUUID:action.callUUID];
    if (!call) {
        [action fail];
        self.isHadReportIncomingCall = NO;
    }
    [Audio configureAudioSession];
    [call answer];//使用的zoom,这里接通zoom
    [action fulfill];
}

注意事项:
1.接听成功要执行fulfill,否则执行fail,更新系统电话界面
4.点击挂断代理回调
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action{
    Call *call = [self.callManager callWithUUID:action.callUUID];
    if (!call) {
        [action fail];
    }
    [Audio stopAudio];
    
    if(call.callState == CallStateActive){
        //接听后挂断callkit
        [[ZoomViewManager shareInstance] leaveVideoCallAndCallKit];
    } else if(call.callState == CallStateConnecting) {
        NSString *pushType = [[NSUserDefaults standardUserDefaults] valueForKey:@"pushType"];
        if ([pushType isEqualToString:@"start"] && [JKNSecurityProvider isUserLogined]){
            NSString *bizNo = [[NSUserDefaults standardUserDefaults] valueForKey:@"bizNo"];
            NSString *sessionId = [[NSUserDefaults standardUserDefaults] valueForKey:@"sessionId"];
            if (call.isAutoEnd) {
                call.isAutoEnd = false;
                //未接听-三分钟自动后自动挂断
                [[ZoomViewManager shareInstance] noAnswerVideoCallWithSessionId:sessionId bizNO:bizNo];
            }else{
                //未接听挂断callkit,用户主动挂断
                [[ZoomViewManager shareInstance] rejectVideoCallWithSessionId:sessionId bizNO:bizNo];
            }
        }
    }
    self.isHadReportIncomingCall = NO;
    self.uuid = nil;
    
    [call end];
    [action fulfill];
    [self.callManager removeCall:call];
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/192673.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人工智能与模式识别的意义(模式识别与图像处理课程作业)

人工智能与模式识别的意义(模式识别与图像处理课程作业一、 人工智能的意义二、 模式识别的意义2.1、文字识别2.2、语音识别2.3、指纹识别2.4、遥感2.5、医学诊断1、语音识别技术2、生物认证技术3、数字水印技术一、 人工智能的意义 人工智能的发送对于我们社会的各个方面都具有…

Word页面中四个直角

文章目录1、四个直角1&#xff09;代表页边距2&#xff09;页边距的设置3&#xff09;打开或关闭“裁剪标记”2、“裁剪标记”与图片1&#xff09;插入图片超过这个角能打印显示出来吗&#xff1f;3、“裁剪标记”与表格1、四个直角 1&#xff09;代表页边距 页面中的四个角代…

【LoRa网关以及LoRa自组网】以“有人物联网”为例

【LoRa网关以及LoRa自组网】以“有人物联网”为例0.参考资料1. LoRa 自组网协议的理解1.1【LoRa模块WH-L101-L-P-H10 】1.2【LoRa网关设置】1.3【节点、网关、服务器通讯】1.4【一些注意事项】1.5【专业名词】2.【LoRa点对点通讯 】LoRa网关可以实现多个LoRa节点的数据采集&…

咖啡商城|基于Springboot+Vue前后端分离咖啡商城系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

微软开源的 5 个 yyds 课程!

微软在 GitHub 开源了五大课程&#xff0c;面向计算机专业或者入门编程的同学。分别是 Web 开发课程、机器学习课程、物联网课程、数据分析课程、Bash 课程。01为初学者准备的 Web开发课程这个 65k Star 的 Web 开发课程由 Microsoft 的 Azure Cloud Advocates 出品&#xff0…

prometheus登录认证

目标 登录Prometheus的9090端口页面的时候&#xff0c;需要输入用户名和密码&#xff0c;才能进入Prometheus页面。 设置密码 Prometheus配置密码不能是明文&#xff0c;必须经过bcrypt程序对密码进行Hash处理。 vim gen-pass.py 内容如下&#xff1a; import getpass import…

Node,docker 中安装node.js

1.启动docker服务 首先启动docker服务:systemctl start docker 2.获取node最新镜像 启动完成之后拉取node最新镜像&#xff1a;docker pull node 然后开始等待&#xff0c;最后拉取完成会有相应的输出信息。 我们再通过命令确认下node是否拉取成功&#xff1a;docker image…

大文件上传和下载解决方案

前言 前端处理 “大” 一直是一个痛点和难点&#xff0c;比如大文件、大数据量。虽然浏览器硬件有限&#xff0c;但是聪明的工程师总是能够最大化利用浏览器的能力和特性&#xff0c;优雅的解决一个个极端问题&#xff0c;满足用户的多样化需求。 断点上传 对于大文件&#…

Linux中的磁盘管理与打包命令

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

2022.11.29(面经五,笔试+技术面)

2022.11.29&#xff08;面经五&#xff09; 笔试题目不难&#xff0c;多刷力扣就成 1.什么是面向对象&#xff1f; 面向对象&#xff1a;是把构成问题的事务分解成各个对象&#xff0c;而建立对象的目的也不是为了完成一个个步骤&#xff0c;而是为了描述某个事物在解决整个问…

应用笔记 | TSMaster核心功能之标定数据的管理

概述标定模块中&#xff0c;标定数据的管理也是其核心功能。主要包括以下方面的内容&#xff1a;标定数据的载入、标定数据导出、标定数据的刷写&#xff0c;以及配套应用程序的刷写等。下面来详细介绍下这些功能。一、标定数据的载入标定数据的载入路径如下&#xff1a;选择目…

Linux网络设备驱动框架

1. 网络设备驱动框架 1.1网际协议分层 优点&#xff1a; 便于封装&#xff1b; 1.2 网络设备驱动程序结构分层 协议接口层&#xff1a; 向网络协议提供统一的数据包发送接口&#xff0c;上层任何形式的协议都通过dev_queue_xmit()发送&#xff0c;通过netif_rx()接收&#xf…

一种用于IDC机房数据挖掘的应用实现

&#xff08;作者单位&#xff1a;华北石油通信有限公司&#xff09;摘要&#xff1a;介绍了适用于数据中心可预定义、自定义场景的轻量级应用实现。现实中监测系统的数据大多沉淀在数据库中&#xff0c;且获取不同设备的信号数据并把这些数据展示出来&#xff0c;多受检测系统…

LabVIEW更高的吞吐量与更少的延迟A

LabVIEW更高的吞吐量与更少的延迟1在设计系统时&#xff0c;“速度”有两个含义。“需要多快采集样品&#xff1f;”通常转化为吞吐量。“样本后需要多快获得结果&#xff1f;”通常转化为延迟。在大多数测量或控制应用中&#xff0c;目标是将真实世界的数据从信号中获取到某种…

LeetCode哈希表相关解法

哈希表1. 理论哈希碰撞的解决方法拉链法线性探测法2. 有效的字母异位词[242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/)3. 两个数组的交集[349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/)4. 快乐数[202. 快乐数](htt…

16_tomcat

tomcat 一、jsp一句话木马 这个东西网上百度就有 <%!class U extends ClassLoader {U(ClassLoader c) {super(c);}public Class g(byte[] b) {return super.defineClass(b, 0, b.length);}}public byte[] base64Decode(String str) throws Exception {try {Class clazz …

Coresight - HW Assisted Tracing on ARM

文章目录一、Introduction二、Acronyms and Classification2.1 Acronyms2.2 Classification三、Device Tree Bindings四、Framework and implementation五、Device Naming scheme六、Topology Representation七、How to use the tracer modules7.1 Using the sysFS interface7.…

如何实现RTMP协议

认识rtmp rtmp是Adobe公司出品的流媒体传输协议&#xff0c;它的全称是Real Time Messaging Protocol&#xff0c;是一个实时消息传输协议&#xff0c;学习RTMP一定要抓住 一个关键点&#xff1a;消息。 rtmp协议的原文可以在Adobe官网下载&#xff0c;内容十分精简&#xff…

用户身份管理(CIAM)如何帮助业务持续增长?|身份云研究院

精明的决策者很早就意识到&#xff0c;数字化转型的核心是为用户提供完善的“数字旅程”&#xff0c;这里的用户包括“员工”和“客户”&#xff0c;而“数字旅程”的核心则是持续提供优质的「数字用户体验&#xff08;DCX&#xff09;」。本文将主要探讨如何制定完善“客户数字…

window版Docker打包镜像并上传到服务器使用

背景&#xff1a;利用jmeter实现自动化进行线上监视&#xff0c;要部署于多台服务器上监视&#xff0c;为了节省时间&#xff0c;方便使用&#xff0c;最终决定使用docker将自动化脚本打包成镜像&#xff0c;这样只要服务器上安装docker环境&#xff0c;直接下载镜像就可以使用…