iOS10 Callkit框架整合总结
Posted WoodBear009
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS10 Callkit框架整合总结相关的知识,希望对你有一定的参考价值。
前不久苹果推出的callkit framework简直就是voip类应用的福音啊,一下把应用的体验提升到了系统级别的高度,加上之前推出的pushkit,形成了一套完整的voip解决方案。正好我们的产品就是一个类voip应用,当然要把callkit加进去,不过callkit刚刚推出,网上的资料实在是少,甚至连官方的api文档里都是大片空白,下面记录一下我对整合callkit的一些总结与理解,也算丰富一下网上的相关资料吧。
一、嵌入后效果
首先,先对Callkit框架嵌入后的实际使用效果有个直观的概念,可以使用手机qq中的qq电话功能(qq已经做了Callkit的框架嵌入)。拨叫、被叫的等业务自己尝试几次,可以发现:
呼出时:直接在qq应用内触发,没有系统电话界面的显示,整个通话过程中的操作都是在qq应用内部完成的,但是通话结束后,系统通话记录中会出现此次对应的通话记录。
被叫时:会显示出系统的电话接听界面,整个通话过程中的操作都可以借助系统通话界面完成,双击home键,可以看到qq应用被启动了,并跳转到了接听界面,用户可以随意切换,通话过程中操作既可以使用系统界面完成,也可以使用qq应用内接听界面完成,最后通话结束后,系统通话记录中会多出此次对应的通话记录。
二、核心类
callkit框架主要围绕的就是CXProvider以及CXCallController两个核心类
关于CXProvider、CXCallController,有文章里是这么描述的:
CXProvider类主要负责 系统->程序方向 的信息、状态传递
CXCallController类主要负责 程序->系统方向的 信息、状态传递
这种描述并没有错误,但是却会让我在有些地方产生困惑,下文我会提到我的困惑,并尝试给出我的理解
三、流程示例
注:不同app的具体实现肯定是各不相同,但会话建立的整体流程应该都是大同小异的,下文的示例是依据我们app的实现写的,看的时候,不要太过纠结于细节或个别环节的差异,应该从整体流程上去体会callkit框架嵌入后带来的变化,最后再结合自己app的实际情况,去做自己的嵌入实现
1.被叫流程
未加入callkit前:
服务器通知app有来电->app显示接听界面->选择接听,进入app通话界面,应答服务器,建立话音通道->通话->挂断电话或被对方挂断
加入callkit框架后:
服务器通知app有来电->调用CXProvider的reportNewIncomingCallWithUUID方法告知系统有来电,系统显示出系统接听界面->选择接听,系统回调performAnswerCallAction,进入app通话界面->系统通知 audiosession可用didActivateAudioSession->应答服务器,建立话音通道->通话->挂断电话或被对方挂断
主要区别:
1.未加入callkit前,接听界面是app提供的,整个流程都掌控在app内部;加入后,首先要调用reportNewIncomingCallWithUUID通知系统有来电,让系统显示出系统接听界面。
2.加入callkit后,用户在系统接听界面选择接听后,不能立即建立话音通道,需要等待didActivateAudioSession的回调
被叫流程代码示例
(1)app调用reportNewIncomingCallWithUUID告知系统有来电,系统显示出系统接听界面
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:callInfo.phoneNum];
callUpdate.remoteHandle = handle;
callUpdate.localizedCallerName = callInfo.name;
callUpdate.supportsDTMF = NO;
callUpdate.supportsHolding = NO;
callUpdate.supportsGrouping = NO;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[_provider reportNewIncomingCallWithUUID:callInfo.uuid update:callUpdate completion:^(NSError * _Nullable error)
if (!error)
NRS_Log(@"0|============================ reportNewInComingCall success");
_callInfo = callInfo;
_callInfo.status = CallInComing;
else
_callInfo = nil;
completion(error);
];
(1.1)[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; 此处,这句不加的话,后面didActivateAudioSession经常会出现不回调的现象,可能是callkit目前的bug
(1.2)reportNewIncomingCallWithUUID:update:completion: 方法调用,app告知系统有来电。此处,我会感到有些困惑,前文不是说CXProvider负责系统->app方向的信息状态传递吗,CXCallController负责app->系统方向,为什么通知系统有来电(app->系统)reportNewIncomingCallWithUUID方法却是属于CXProvider呢?
我的理解是,我们把状态、信息划分为由外部(app外)触发和自身内部(app内)发起两类。
来电行为,显然应该是一个外部触发行为,有人给你打电话来了,是从服务器(外部)通知给你的客户端,这种由外向内的事情,包括后面一些系统接听界面、通话界面的事件触发回调,都由CXProvider负责;
而比如拨打电话(客户端发起),应用内一些按钮点击、状态变化告知系统,这种由内向外的事情,都由CXCallController负责。
(2)选择接听,系统触发回调performAnswerCallAction,告知你用户选择了接听(answer)
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
这里面你可以加载出你的通话界面
这类action的相关回调中,如果回调处理函数执行一切正常,要调用一下[action fulfill];有错误,则调用[action fail]
(3)上个回调后,系统立刻会回调
- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession
此处,你就可以应答服务器并建立起话音通道,进行会话了
(4)挂断电话 (4.1)在系统通话界面挂断的电话,会触发performEndCallAction:回调,在这里你可以添加相关挂断处理代码
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action
(4.2)被对方挂断或在应用内挂断电话,调用CXProvider的reportCallWithUUID:endedAtDate:reason:方法,不同的CXCallEndedReason可以区分出不同的通话结束原因(被挂、主动挂断等)
[_provider reportCallWithUUID:_callInfo.uuid endedAtDate:nil reason:reason];
2.主叫流程
未加入callkit前:
向服务器发起主叫请求->显示通话界面->对方回应 ->建立话音通道,进行会话->挂断电话或被对方挂断
加入callkit框架后:
利用CXCallController告知系统发起一个主叫动作->系统回调performStartCallAction,进入通话界面,向服务器发起主叫请求->对方回应->建立话音通道,进行会话->挂断电话或被对方挂断
主叫流程代码示例
(1)利用CXCallController告知系统发起一个主叫动作,通过CXCallController的requestTransaction发起一个startCall动作
CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:callInfo.phoneNum];
CXStartCallAction *action = [[CXStartCallAction alloc] initWithCallUUID:callInfo.uuid handle:handle];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[_callController requestTransaction:transaction completion:^(NSError * _Nullable error)
if (!error)
NRS_Log(@"0|============================ reportOutGoingCall success");
_callInfo = callInfo;
_callInfo.status = CallOuting;
else
NRS_Log(@"0|============================ reportOutGoingCall fail");
_callInfo = nil;
completion(error);
];
(2) 系统触发回调performStartCallAction
-(void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action
这里面你可以加载出你的通话界面,并向服务器发起主叫请求了。
(3)向服务器发起主叫请求,并等待对方回应
-(void)callFrom:(NSString *)number to:(NSString *)toNumber
callback:(void (^)(BOOL isSuc)) callback
if(iOS10)
[_callKitManager reportStartingConnection];
[CallService callFrom:number to:toNumber callback:^(BOOL isSuc)
if (iOS10)
if(isSuc)
[_callKitManager reportConnectionConnected];
else
[_callKitManager callByeForReason:CXCallEndedReasonFailed];
callback(isSuc);
];
-(void)reportStartingConnection
if(_callInfo && _callInfo.status == CallOuting)
[_provider reportOutgoingCallWithUUID:_callInfo.uuid startedConnectingAtDate:nil];
//在系统通话记录中更新出具体名字
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
callUpdate.localizedCallerName = _callInfo.name;
[_provider reportCallWithUUID:_callInfo.uuid updated:callUpdate];
-(void)reportConnectionConnected
if(_callInfo && _callInfo.status == CallOuting)
[_provider reportOutgoingCallWithUUID:_callInfo.uuid connectedAtDate:nil];
(3.1)发起主叫请求,并等待对方回应过程中,会调用
reportOutgoingCallWithUUID:startedConnectingAtDate:(这个方法并不是必须调用的)
reportOutgoingCallWithUUID:connectedAtDate:
这两个函数主要是为了更精确的告知系统实际有效的通话时长(连接过程,不应算在通话时长中),以便更好的在通话记录中显示
(3.2)回看步骤(1)这部分代码时,你会发现没有方式去告知系统被叫方的名称,此处通过CXCallUpdate,将被叫方姓名告知系统,以便在通话记录中更好显示
(4)建立话音通道,进行会话
(5)挂断电话或被对方挂断,调用CXProvider的reportCallWithUUID:endedAtDate:reason:方法。
3.关于挂断处理的一些说明
在callkit中还有一个对应的endCall动作,我看demo或别的资料里在结束通话后都向系统发出了endCall动作,但在我的最终整合中,我并未主动去抛出过这个动作(都是通过reportCallWithUUID:endedAtDate:reason方法告知系统通话结束的),主要原因如下:
挂断分以下几种情况:
1.被叫时在系统通话界面点击挂断,这种情况发生在程序外部,系统先于app得知挂断的产生,然后系统会回调performEndCallAction:方法反馈给app,app再做挂断处理
2.其余情况,被对方挂断,或是在app内点击挂断按钮这些情况,都是app先于系统得知挂断的产生,app直接就可以立刻对挂断进行处理了,没有必要再去抛出一个action,然后又通过回调返回客户端再去做处理
注:action抛出后,都会触发相应的回调。
3.当然,从流程尽量统一的设计原则上讲,抛出这个endAction动作,然后统一在performEndCallAction:中做挂断处理是比较合理的,但就我的项目实际而言,这部分改动相对有些麻烦,于是我就没有去做相应调整。
目前来看,我没有去使用endCallAction,也未出现任何的问题。
3.通讯录对接
callkit嵌入后,在系统通讯录中就会出现用你的app产生的通话记录,如果用户点击了相应记录,你的app应该可以被启动,并发出拨叫,要实现这个功能,只需要进行以下工作:
实现appdelegate的方法
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler
INInteraction *interaction = [userActivity interaction];
INIntent *intent = interaction.intent;
if ([intent isKindOfClass:[INStartAudioCallIntent class]])
INStartAudioCallIntent *audioIntent = (INStartAudioCallIntent *)intent;
INPerson *person = audioIntent.contacts.firstObject;
NSString *phoneNum = person.personHandle.value;
[self reportOutGoingCall:phoneNum];
解析出对应的电话号码,发起主叫流程即可
4.其他功能对接与支持
系统通话界面里的分组、静音、dtmf、免提(不用代码处理)等等功能,callkit都提供了相应的对接方式,这里不再赘述。
以上是关于iOS10 Callkit框架整合总结的主要内容,如果未能解决你的问题,请参考以下文章
如何在 iOS 10 上使用 CallKit 显示来电显示(添加标识条目)