CallKit framework(<CallKit/CallKit.h>)是蘋果在2016年推出iOS10系統(tǒng)時的新功能,可以調(diào)起系統(tǒng)的接聽頁進行音視頻通話。目前市面上使用該方案的的APP不是很多张弛,所有中國區(qū)在App Store上架的App都不能支持CallKit功能,官方文檔和網(wǎng)上資料對相關(guān)功能的細節(jié)介紹都很有限灯荧。
講CallKit之前不得不先說一下VoIP迹恐,VoIP是什么挣惰?
VoIP(<PushKit/PushKit.h>)是一種新的push通知類型,是蘋果在iOS8中新引入的PushKit框架殴边。
VoIP push和普通APNS push有什么區(qū)別憎茂?
最明顯的區(qū)別就是:APP收到VoIP push消息時會直接將已經(jīng)殺掉的APP激活!
<CallKit/CallKit.h>與<PushKit/PushKit.h>這兩個庫配合使用锤岸,即可形成了一套APP在殺死的情況下竖幔,收到音視頻邀請時持續(xù)響鈴的完整解決方案。
PushKit
PushKit的使用有3步操作是偷,首先遵守<PKPushRegistryDelegate>協(xié)議:
1.注冊拳氢, 在APP啟動代碼里,通過PKPushRegistry注冊VoIP服務(wù)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushRegistry.delegate = self;
pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
return YES;
}
2. 獲取token蛋铆,協(xié)議方法里面獲取token并上傳給對應的服務(wù)馋评,通過Token來進行個推的
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
// 一般做法
// NSString *str = [NSString stringWithFormat:@"%@",credentials.token];
// NSString *tokenStr = [[[str stringByReplacingOccurrencesOfString:@"<" withString:@""]
stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
}
3. 接收VoIP消息,收到VoIP消息刺啦,調(diào)用CallKit留特,做剩余的通訊邏輯處理
// iOS11之前收到VoIP推送后執(zhí)行的回調(diào)
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
[self handleVoipPushMessageWithPayload:payload forType:type];
}
// iOS11之后收到VoIP推送后執(zhí)行的回調(diào)
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
[self handleVoipPushMessageWithPayload:payload forType:type];
if (completion) {
completion();
}
}
其他...
// token失效時回調(diào),一般只做打印處理
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type {
NSLog(@"之前提供的token失效洪燥,則調(diào)用此方法");
}
- (void)handleVoipPushMessageWithPayload:(PKPushPayload *)payload forType:(PKPushType)type {
//開啟后臺任務(wù)(實踐發(fā)現(xiàn)如果不開啟后臺任務(wù)磕秤,調(diào)不起系統(tǒng)的CallKit接聽界面)
[self startBgTask];
NSDictionary *dic = payload.dictionaryPayload;
// 這里調(diào)用CallKit
}
// 開啟后臺延時
- (void)startBgTask {
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
}];
}
CallKit
CallKit主要有:CXProvider、CXCallController 2個核心類
CXProvider類用于被叫流程捧韵,主要負責系統(tǒng)-->APP的信息市咆、狀態(tài)傳遞
CXCallController類用于主叫流程,主要負責APP-->系統(tǒng)的信息再来、狀態(tài)傳遞
因為APP業(yè)務(wù)只用到被叫功能蒙兰,所以下面只講被叫流程磷瘤。
CXProvider
主要作用有三個:
- 調(diào)起系統(tǒng)的電話接聽頁面
- 銷毀系統(tǒng)的電話接聽頁面
- 代理方法中接收用戶的操作事件,如接聽搜变、掛斷..
APP內(nèi)是如何調(diào)起系統(tǒng)的接聽頁面的采缚?
self.callUpdate.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:self.channelId];;
self.callUpdate.hasVideo = NO;
self.callUpdate.localizedCallerName = self.netCallDict[SFIMVoip_FromName];
__weak __typeof(self) wself = self;
[self.provider reportNewIncomingCallWithUUID:[NSUUID UUID]
update:self.callUpdate
completion:^(NSError *_Nullable error) {
if (error) {
NSLog(@"通話創(chuàng)建失敗 current error %@", error.userInfo);
//通話創(chuàng)建失敗
[wself resetVariableData];
}
}];
調(diào)起CallKit接聽頁面過程中,用到了CXHandle挠他、CXCallUpdate扳抽、CXProvider、CXProviderConfiguration四個類
CXProvider的初始化及代理設(shè)置
self.provider = [[CXProvider alloc] initWithConfiguration:self.configuration];
[self.provider setDelegate:self queue:nil];
CXProvider的詳細用法
//初始化方法 使用CXProviderConfiguration來進行配置
- (instancetype)initWithConfiguration:(CXProviderConfiguration *)configuration;
//設(shè)置代理與代理函數(shù)所工作的線程
- (void)setDelegate:(nullable id)delegate queue:(nullable dispatch_queue_t)queue;
//向系統(tǒng)發(fā)起一個新的通話請求
/*
UUID為此通話請求的標識 可以使用它來關(guān)閉通話
update設(shè)置界面的更新參數(shù)
*/
- (void)reportNewIncomingCallWithUUID:(NSUUID *)UUID update:(CXCallUpdate *)update completion:(void (^)(NSError *_Nullable error))completion;
//結(jié)束某個通話 使用上面的UUID作為標識
- (void)reportCallWithUUID:(NSUUID *)UUID endedAtDate:(nullable NSDate *)dateEnded reason:(CXCallEndedReason)endedReason;
CXProviderConfiguration
Provider的配置殖侵,例如設(shè)置通訊服務(wù)名稱贸呢,鈴聲,圖標
//設(shè)置服務(wù)名稱
@property (nonatomic, readonly, copy) NSString *localizedName;
//設(shè)置鈴聲 資源必須在 app的 bundle里
@property (nonatomic, strong, nullable) NSString *ringtoneSound;
//設(shè)置應用圖標
@property (nonatomic, copy, nullable) NSData *iconTemplateImageData;
//設(shè)置最大支持的組數(shù) 默認為2
@property (nonatomic) NSUInteger maximumCallGroups;
//設(shè)置最大的每組人數(shù) 默認為5
@property (nonatomic) NSUInteger maximumCallsPerCallGroup;
//設(shè)置是否將通話記錄保存進最近通話列表
@property (nonatomic) BOOL includesCallsInRecents;
//設(shè)置是否支持視頻通話
@property (nonatomic) BOOL supportsVideo;
//設(shè)置支持的操作類型
@property (nonatomic, copy) NSSet *supportedHandleTypes;
CXHandle中來定義操作的類型拢军,通話對方的信息
//類型
/*
typedef NS_ENUM(NSInteger, CXHandleType) {
CXHandleTypeGeneric = 1,//通用
CXHandleTypePhoneNumber = 2,//電話
CXHandleTypeEmailAddress = 3,//郵箱地址
} API_AVAILABLE(ios(10.0));
*/
@property (nonatomic, readonly) CXHandleType type;
//值
@property (nonatomic, readonly, copy) NSString *value;
- (instancetype)initWithType:(CXHandleType)type value:(NSString *)value
CXCallUpdate類
有點類似CXProviderConfiguration,也是一些配置信息
//遠程操作對象 如果是接收方 則此為呼叫方
@property (nonatomic, copy, nullable) CXHandle *remoteHandle;
//名稱
@property (nonatomic, copy, nullable) NSString *localizedCallerName;
//是否支持暫時掛起
@property (nonatomic) BOOL supportsHolding;
//是否支持組
@property (nonatomic) BOOL supportsGrouping;
//是否支持非組通話
@property (nonatomic) BOOL supportsUngrouping;
//是否支持DTMF
@property (nonatomic) BOOL supportsDTMF;
//是否包含視頻
@property (nonatomic) BOOL hasVideo;
可以動態(tài)更改provider的配置信息
- (void)reportCallWithUUID:(NSUUID *)UUID updated:(CXCallUpdate *)update;
CXProviderDelegate
按鈕的回調(diào)方法(每個回調(diào)結(jié)束的時候要執(zhí)行[action fulfill]
楞陷,如果邏輯處理異常則調(diào)用fail
和timeout
函數(shù)提示通話失敗)
// 點擊接聽按鈕的回調(diào)
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action;
// 點擊結(jié)束按鈕的回調(diào)(結(jié)束或拒絕通話)
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action;
結(jié)束通話茉唉,銷毀CallKit
[self.provider reportCallWithUUID:self.currentUUID endedAtDate:nil reason:CXCallEndedReasonRemoteEnded];
CallKit和VoIP的技術(shù)并不難固蛾,只是開發(fā)過程中調(diào)試比較麻煩、浪費時間度陆。因為涉及到推送沒法在模擬器上斷點調(diào)試艾凯,只能打包真機安裝驗證,所以建議開發(fā)過程中一定要多加打印日志坚芜,因為后期只能通過日志觀察览芳、解決問題斜姥。
歡迎留言交流....
參考資料:
iOS10適配之 CallKit