iOS用CallKit实现来电识别、来电拦截

简介: 前言最近需要实现一个新需求,用iOS 10出的CallKit实现将APP的通讯录的信息同步到系统中,可以不把人员信息加到通讯录中,实现来电号码识别。

前言

最近需要实现一个新需求,用iOS 10出的CallKit实现将APP的通讯录的信息同步到系统中,可以不把人员信息加到通讯录中,实现来电号码识别。这个功能在xx安全卫士、xx管家中很早就实现了,但是网上相关的资料较少,而且官方的文档写的太简单了,很多坑还要自己去摸索。于是记录一下和各位分享,如有错误之处请各位指出!

PS: 先说个题外话吧,CallKit功能在iOS 10的时候还不太稳定,iOS 10刚出来的时候为了体验骚扰拦截功能,手贱装了两个不同的拦截APP,然后就悲剧了。盗一张网上的图:

img_f6a2f8a71a755f1757fcf606e763254b.png
1.png

然后各种重启、重装APP都没有用,写的Demo也跑不起来,唯一的办法只有重置系统。说多了都是泪!

一、Call Directory app extension

实现来电识别、来电拦截功能需要使用CallKit当中的Call Directory app extension,首先,需要了解extension。关于extension网上有很多教程,这里就不细说了。推荐两篇文章,英文好的推荐看官方文档,还有一篇中文博客

使用Call Directory Extension主要需要和3个类打交道,分别是
CXCallDirectoryProviderCXCallDirectoryExtensionContextCXCallDirectoryManager

img_67ba43f03685186db243edf24988e286.png
2.png

本文涉及的Demo

CXCallDirectoryProvider

官方文档:The principal object for a Call Directory app extension for a host app.

正如官方文档所说,这是Call Directory app extension最重要的一个类。
用系统模板新建Call Directory Extension之后会自动生成一个类,继承自CXCallDirectoryProvider。入口方法:

// 有两种情况改方法会被调用
// 1.第一次打开设置-电话-来电阻止与身份识别开关时,系统自动调用
// 2.调用CXCallDirectoryManager的reloadExtensionWithIdentifier方法会调用
- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context {
    context.delegate = self;
    // 添加号码识别信息与号码拦截列表
    [self addIdentificationPhoneNumbersToContext:context];
    [context completeRequestWithCompletionHandler:nil];
}

CXCallDirectoryExtensionContext

官方文档:A programmatic interface for adding identification and blocking entries to a Call Directory app extension.
CXCallDirectoryExtensionContext objects are not initialized directly, but are instead passed as arguments to the CXCallDirectoryProvider instance method beginRequestWithExtensionContext:.

大致意思就是说,这是一个为Call Directory app extension添加号码识别、号码拦截的入口。CXCallDirectoryExtensionContext不需要自己初始化,它会作为CXCallDirectoryProviderbeginRequestWithExtensionContext函数的参数传递给使用者。
它的主要方法有两个:

// 设置号码识别信息
- (void)addIdentificationEntryWithNextSequentialPhoneNumber:(CXCallDirectoryPhoneNumber)phoneNumber label:(NSString *)label;

// 设置号码拦截列表
- (void)addBlockingEntryWithNextSequentialPhoneNumber:(CXCallDirectoryPhoneNumber)phoneNumber;

在设置时候要注意:

  1. 号码不能重复,不然会报错CXErrorCodeCallDirectoryManagerErrorDuplicateEntries
  2. 号码必须按照升序写入,不然会报错CXErrorCodeCallDirectoryManagerErrorEntriesOutOfOrder
  3. 号码必须格式化后传入,手机号码必须加上国家码,例如18012341234就不行,需要加上86,构造成8618012341234;固话需要格式为:国家码+区号(去掉第一个0)+号码,例如010-61001234格式化之后为,861061001234。如果号码格式错误,会导致识别不出来。
  4. 上限数据是200万(在其它文章里看到的,然后自己测试了下,构造了200万条数据写入的时候会报错CXErrorCodeCallDirectoryManagerErrorMaximumEntriesExceeded,150万条数据是OK的,所以这个数据上限一定要注意。实测安装了XX安全卫士、XX管家实现骚扰电话拦截用了3个extension,可能数据量太大就是一个原因。)
  5. 在用户第一次打开设置时,会调用beginRequestWithExtensionContext,这时候不宜写太多数据,不然会卡在设置那里转圈,用户体验很差。可以先写部分数据,然后回到主APP了调用reloadExtensionWithIdentifier去刷新。

CXCallDirectoryManager

官方文档:The programmatic interface to an object that manages a Call Directory app extension.

CXCallDirectoryManager主要作用是管理Call Directory app extension。
有两个方法:

// 重新设置号码识别、电话拦截列表
// 调用该方法后会重置之前设置的列表,然后调用beginRequestWithExtensionContext:
- (void)reloadExtensionWithIdentifier:(NSString *)identifier completionHandler:(nullable void (^)(NSError *_Nullable error))completion;

// 获取extension是否可用,需要在“设置-电话-来电阻止与身份识别"中开启权限
- (void)getEnabledStatusForExtensionWithIdentifier:(NSString *)identifier completionHandler:(void (^)(CXCallDirectoryEnabledStatus enabledStatus, NSError *_Nullable error))completion;

二、实战

先上Demo地址。下面会一步步讲解。

创建extension

新建一个Target(File-New-Target)。

img_eb74f991fbb1e08e0e709af7d8146b9c.png
3.png

会自动建立一个目录,默认有三个文件。在.m文件中有系统给出的示例代码

img_e8c093e95e605343acac80f6e1b404c4.png
4.png

我们来看看系统的模板代码,首先是入口函数

- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context {
    context.delegate = self;
    if (context.isIncremental) {
        [self addOrRemoveIncrementalBlockingPhoneNumbersToContext:context];

        [self addOrRemoveIncrementalIdentificationPhoneNumbersToContext:context];
    } else {
        [self addAllBlockingPhoneNumbersToContext:context];

        [self addAllIdentificationPhoneNumbersToContext:context];
    }
    
    [context completeRequestWithCompletionHandler:nil];
}

CallDirectoryHandler

我在Xcode 9生成的代码,context.isIncremental是iOS 11才增加的,还有所有的remove的方法也是iOS 11才有的,为了适配iOS 10,还是不推荐使用。
系统模板代码大致逻辑就是,先添加号码识别、号码拦截记录,添加完成后调用completeRequestWithCompletionHandler:完成整个过程。
由于号码拦截比较简单,只是写入一个号码的数组,本文就以号码识别为例,号码识别方法系统模板这么写的:

- (void)addAllIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context {
    CXCallDirectoryPhoneNumber allPhoneNumbers[] = { 8618788888888, 8618885555555 };
    NSArray<NSString *> *labels = @[ @"送餐电话", @"诈骗电话" ];
    NSUInteger count = (sizeof(allPhoneNumbers) / sizeof(CXCallDirectoryPhoneNumber));
    for (NSUInteger i = 0; i < count; i += 1) {
        CXCallDirectoryPhoneNumber phoneNumber = allPhoneNumbers[i];
        NSString *label = labels[i];
        [context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label];
    }
}

这么多代码,核心就是一行[context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label];,注意phoneNumber是CXCallDirectoryPhoneNumber类型,其实就是long long类型。
在这个函数里,需要把需要识别的号码和识别信息,一条一条的写入

检查授权

开启extension功能需要在“设置-电话-来电阻止与身份识别”中开启,我们在写入数据时第一步是引导用户给我们的extension授权。

    CXCallDirectoryManager *manager = [CXCallDirectoryManager sharedInstance];
    [manage
     getEnabledStatusForExtensionWithIdentifier:self.externsionIdentifier
     completionHandler:^(CXCallDirectoryEnabledStatus enabledStatus, NSError * _Nullable error) {
         // 根据error,enabledStatus判断授权情况
         // error == nil && enabledStatus == CXCallDirectoryEnabledStatusEnabled 说明可用
         // error 见 CXErrorCodeCallDirectoryManagerError
         // enabledStatus 见 CXCallDirectoryEnabledStatus
     }];

写入数据

用户在设置开启后,调用reloadExtensionWithIdentifier即可触发CallDirectoryHandler更新数据逻辑。

    CXCallDirectoryManager *manager = [CXCallDirectoryManager sharedInstance];
    [manager reloadExtensionWithIdentifier:self.externsionIdentifier completionHandler:^(NSError * _Nullable error) {
        // error 见 CXErrorCodeCallDirectoryManagerError
    }];

验证

接下来在真机下跑下(一定要在插了电话卡的iPhone上调试,模拟器不行!),写入成功后,打开电话,拨号18788888888,提示”送餐电话”。说明写入成功!

img_545d4c6a7bddf9f44ee530208c169d59.png
5.png

三、extension和containing app数据共享

上面的步骤中,号码信息是写死在代码中的,在实际应用中这些号码信息肯定不是写死的,一般需要从服务器获取。这就需要我们的APP与extension进行通信,需要用到APP Groups,怎么用网上有很多文章了,我就不多说了,推荐一篇
其实本质就是通过APP Groups,开辟一片空间,extension和containing app都可以访问,然后我们的APP就可以通过NSUserDefaults、文件、数据库等方式共享数据给extension了。前期我使用过NSUserDefaults,效率很低,大概在5万数据的时候就爆内存了,使用extension一定要注意内存,不然很容易被系统干掉,所以不推荐使用这种方式。
Demo中采用的是读写文件的方式,大致思路(具体实现看Demo):

  1. 在APP中把数据序列化之后写到一个文件中
  2. 在extension中读取这个文件,读取一行,调用一次addIdentificationEntryWithNextSequentialPhoneNumber,然后及时释放
    这种方式理论上是可以达到最大限制200w条的(实际测试150万没有问题)。

获取APP Groups文件路径

NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:self.groupIdentifier];
    containerURL = [containerURL URLByAppendingPathComponent:@"CallDirectoryData"];
    NSString* filePath = containerURL.path;

进度监控

在xx安全卫士中,开启骚扰电话拦截功能有一个进度条,非常的直观。但是在extension中是没法更新UI的,有一种实现方式,可以用开源框架MMWormhole来实现APP与extension通信,然后把进度从extension传到APP中,在APP中更新进度条。理论上该方案是可行的,感兴趣的同学可以尝试下。

关于上架

在加上CallKit第一次上架时,收到了苹果的拒信,说CallKit在中国区被禁止了。

img_c12c53f0e444a964efbb6c02c664aa70.png
fileUpload.jpeg

后来观察了下xx安全卫士一直在正常更新,猜想苹果只是禁止了CallKit关于VOIP这部分功能。
遇到苹果的拒信不要慌,录制一个来电识别的功能演示视频,放到Youtube,然后在备注里面解释一下,就可以通过审核了。

给一个审核备注模板:

关于CallKit,我们遵守中国的法律,没有使用VOIP,我们仅使用了Call Directory。我们应用内有一个公司内部通讯录,为了增加用户员工沟通效率,我们在电话页面自动识别内部通讯录的人员 ,并展示姓名与岗位。我们录制了一个演示视频:视频地址


博客地址

目录
相关文章
|
11月前
|
机器学习/深度学习 API iOS开发
iOS MachineLearning 系列(17)—— 几个常用的对象识别 CoreML 模型
上一篇文章中,我们介绍了几个官方的图片分类的模型,图片分类模型的应用场景在于将图片中最主要的事物进行识别,在已有的词库中找到最可能得事物。而对象识别则要更高级一些。再之前的文章,我们介绍过可以使用官方提供的API来进行矩形识别,文本识别,二维码识别以及人脸识别等,这类识别功能的特点是我们不仅可以将图片中的物体位置和尺寸分析出来,还可以对其进行类别的分类。
250 0
|
算法 API iOS开发
iOS MachineLearning 系列(3)—— 静态图像分析之区域识别
本系列的前一篇文章介绍了如何使用iOS中自带的API对图片中的矩形区域进行分析。在图像静态分析方面,矩形区域分析是非常基础的部分。API还提供了更多面向应用的分析能力,如文本区域分析,条形码二维码的分析,人脸区域分析,人体分析等。本篇文章主要介绍这些分析API的应用。
217 0
|
安全 数据安全/隐私保护 iOS开发
iOS小技能:【发红包】使用tweak和lua脚本结合进行实现
我们开发的大部分越狱程序,都是编译成动态链接库(`例如:介绍的越狱程序(Tweak)开发,就是动态链接库。`),然后通过越狱平台的MobileSubstrate(iOS7上叫CydiaSubstrate)来加载进入目标程序(Target),通过对目标程序的挂钩(Hook),来实现相应的功能。
262 0
|
11月前
|
自然语言处理 iOS开发 开发者
iOS MachineLearning 系列(11)—— 自然语言识别与文本分析
在上一篇文章中,我们介绍了使用NaturalLanguage框架来进行自然语言的拆解,可以将一段文本按照单词,句子或段落的模式进行拆解。并且,在进行拆解时,其可以自动的识别所使用的语言。
170 0
|
人工智能 API iOS开发
iOS MachineLearning 系列(2)—— 静态图像分析之矩形识别
本系列文章将完整的介绍iOS中Machine Learning相关技术的应用。本篇文章开始,我们将先介绍一些与Machine Learning相关的API的应用。使用这些API可以快速方便的实现很多如图像识别,分析等复杂功能,且不会增加应用安装包的体积。
223 0
|
移动开发 JavaScript weex
weex-自定义module,实现weex在iOS的本地化,js之间互相跳转,交互,传值(iOS接入weex的最佳方式)
weex-自定义module,实现weex在iOS的本地化,js之间互相跳转,交互,传值(iOS接入weex的最佳方式)
219 0
|
存储 数据处理 iOS开发
iOS开发-本地推送实现方法和数据处理方案(二)
iOS开发-本地推送实现方法和数据处理方案(二)
168 0
|
存储 数据处理 iOS开发
iOS开发-本地推送实现方法和数据处理方案(一)
iOS开发-本地推送实现方法和数据处理方案(一)
209 0
|
iOS开发
iOS开发 - 不通过import引入类名实现push或present
iOS开发 - 不通过import引入类名实现push或present
75 0
|
Android开发 iOS开发
iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。
iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。
354 0
iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。