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

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

前言

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

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

1.png

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

一、Call Directory app extension

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

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

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)。

3.png

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

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 *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,提示”送餐电话”。说明写入成功!

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在中国区被禁止了。

fileUpload.jpeg

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

给一个审核备注模板:

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


博客地址

目录
相关文章
|
安全 JavaScript Java
iOS使用PushKit实现VoIP
iOS使用PushKit实现VoIP
1387 0
|
1月前
|
编解码 Linux Android开发
安卓手机投屏电脑端教程,手机投屏教程,可以手机和电脑互传文件。电脑管理手机文件和APP等操作
QtScrcpy是一款基于Scrcpy开发的跨平台安卓投屏工具,支持Windows、macOS、Linux系统。无需在手机安装应用,通过USB或Wi-Fi连接即可实现高清低延迟投屏,支持文件互传、屏幕录制、截图、多设备管理等功能,操作简便,适合开发者与普通用户使用。
430 47
|
文字识别 API iOS开发
iOS小技能:iOS13 证件扫描 & 文字识别API
1. 应用场景:证件扫描、文字识别 2. 原理:利用iOS13 VNDocumentCameraViewController的证件扫描和VNRecognizeTextRequest文字识别功能进行实现
636 0
iOS小技能:iOS13 证件扫描 & 文字识别API
|
前端开发 Java 数据库连接
Spring Boot 3 整合 Mybatis-Plus 动态数据源实现多数据源切换
Spring Boot 3 整合 Mybatis-Plus 动态数据源实现多数据源切换
|
编解码 Android开发 数据安全/隐私保护
Android平台外部编码数据(H264/H265/AAC/PCMA/PCMU)实时预览播放技术实现
好多开发者可能疑惑,外部数据实时预览播放,到底有什么用? 是的,一般场景是用不到的,我们在开发这块前几年已经开发了非常稳定的RTMP、RTSP直播播放模块,不过也遇到这样的场景,部分设备输出编码后(视频:H.264/H.265,音频:AAC/PCMA/PCMU)的数据,比如无人机或部分智能硬件设备,回调出来的H.264/H.265数据,除了想转推到RTMP、轻量级RTSP服务或GB28181外,还需要本地预览甚至对数据做二次处理(视频分析、实时水印字符叠加等,然后二次编码),基于这样的场景诉求,我们开发了Android平台外部编码数据实时预览播放模块。
294 0
|
Android开发 架构师
Android:动态更换桌面ICON
前言 当老板和产品提出这种需求的时候,我并不感到害怕,心里甚至有点窃喜,因为大厂基本都有这种效果,那肯定也好实现。当我一查资料的时候,发现情况不容乐观。
4673 0
|
人工智能 测试技术 开发者
北大李戈团队提出大模型单测生成新方法,显著提升代码测试覆盖率
【9月更文挑战第27天】北京大学李戈团队在人工智能领域取得重要突破,提出HITS新方法,通过将待测方法分解为多个切片并利用大型语言模型逐个生成测试用例,显著提升代码测试覆盖率,尤其在处理复杂方法时效果显著,为软件开发和测试领域带来新希望。尽管存在一定局限性,HITS仍展示了巨大潜力,未来有望克服限制,推动软件测试领域的创新发展。论文详情见【https://www.arxiv.org/pdf/2408.11324】。
615 6
|
9月前
|
人工智能 JavaScript 前端开发
【最佳实践系列】AI程序员让我变成全栈:基于阿里云百炼DeepSeek的跨语言公告系统实战
本文介绍了如何在Java开发中通过跨语言编程,利用阿里云百炼服务平台的DeepSeek大模型生成公告内容,并将其嵌入前端页面。
617 10
|
图形学
unity之Layout Group居中显示
unity实现Layout Group居中显示
unity之Layout Group居中显示
|
机器学习/深度学习 人工智能 算法
构建高效AI系统:深度学习优化技术解析
【5月更文挑战第12天】 随着人工智能技术的飞速发展,深度学习已成为推动创新的核心动力。本文将深入探讨在构建高效AI系统中,如何通过优化算法、调整网络结构及使用新型硬件资源等手段显著提升模型性能。我们将剖析先进的优化策略,如自适应学习率调整、梯度累积技巧以及正则化方法,并讨论其对模型训练稳定性和效率的影响。文中不仅提供理论分析,还结合实例说明如何在实际项目中应用这些优化技术。