iOS利用Callkit实现来电提醒

iOS利用Callkit实现来电提醒

标签(空格分隔): 个人经验


前言

Callkit框架可以让开发者通过它实现许多电话识别(有很多无法识别后面说).最近公司需要实现App里面通讯里面所以联系人的手机座机以及短号实现电话识别,以及骚扰数据库里面号码140万条数据实现电话识别,本人也是看遍各种文档,踩过各种坑,对于网上的很多的文章基本上都是讲的比较简单.

简单的CallKit使用就不讲了给上链接

iOS10:CallKit的简单应用


简介

Callkit每个应用可以有许多个,具体上限,我试过3个是可以用的.
这篇文章分为单个和2个讲(其实3个以上和2个写法和注意点一样).

注意

这里考虑到不管是通讯录还是骚扰号码都是会增量更新的所以用的是sqlite数据库作为实例,不是本地文件.

数据库创建

//这里面创建的数据库表最好越简单越好,最好一个Callkit对应一个数据库,最好是我这样,需要一个自增的id,号码为你的所有号码都加上86,number最好需要加上UNIQUE,没有加的话给你个办法,现将number改为Text,然后update所有的数据加上86再变成Integer,改完后记得数据库Vacuum.最后一个字段来显示收到这个号码时你手机需要显示的提醒,实例:XXX(App名字)标记为:骚扰号码(App识别的名称).这样你往里面写就直接是number对应一个name,不需要什么操作比较简单.

CREATE TABLE tixingcaiyin(id INTEGER PRIMARY KEY, number Integer UNIQUE , name TEXT)

解释:1.为什么要用简单数据库?

数据库里的表越简单,当存在几十万甚至上百万的时候,数据库体积比较小,一般骚扰电话100多万条数据很正常,记住这个数据库你是需要压缩放在App里面的,当程序启动时将其解压,然后复制放到和Callkit的公共的App Groups里面(通过一个id来绑定,下面上图),方便Callkit来读取写入,如果你数据超过10万,别说你不放底包请求数据,150万你会请求多久~~~测试94万数据库大小46.0兆压缩下14.6兆放入App中增加14兆.

iOS利用Callkit实现来电提醒_第1张图片
image_1bnf99vlq1l971gq2srd1onr140819.png-127.6kB

解释:2.为什么最好一个Callkit对应一个数据库?

多个Callkit是可以同时进行写入的,一旦都是用的同一个App Groups,会出现多个Callkit用同一个数据操作,可能会出现不可预知的bug.(这里也推荐几个Callkit用几个App Group互不干扰).

iOS利用Callkit实现来电提醒_第2张图片
image_1bnf9jglqm684o8tet61icf99.png-144.7kB

第一个Callkit用的是分享的App Groups 这个没有关系.

解释:2.为什么号码的字段需要为number?号码要加上86?号码最好加上UNIQUE?

1.号码用number原因: 写入到Callkit扩展里面的号码一定是从小到大的顺序排序的,添加顺序为优先添加小的号码。例如:1234567890和1324567890两个号码要先添加1234567890,然后添加1324567890.而且你不可能一下子搜出所有的号码放到数组里面进行排序,因为数据一但达到几万几十万甚至几百万你的程序早就崩溃了.所有搜索的时候需要加上限制按照从小到大一次搜出一定的条数(我一班喜欢20000),如果用Text在出现位数差时会出现排序错误导致写入错误.

2.号码要加上86?原因:手机号码前面需要添加国家码,固话需要带有区号,然后区号去零,然后前面添加国家码。譬如手机号:185XXXXX8497 需要格式化为86185XXXXX8497;固话 0755-12345678 需要格式化为:8675512345678(实际测试座机第一个0去掉和不去掉都行).

3.号码最好加上UNIQUE?原因:号码字段加上UNIQUE就是防止号码号码出现重复,苹果爸爸规定写入的号码不能出现重复.

我的数据库图:

iOS利用Callkit实现来电提醒_第3张图片
image_1bnfa701l14tttnrc7d1t4h41hp.png-68.2kB

数据库的使用这里已写入90多万为例子


//这里面解释下为什么都将数据拼接好放到数据库,因为写入的这个方法循环是循环几十万上百万次数的,而且这个扩展可以使用内存只用20兆,实验内存超过18兆直接写入失败,所以变量都是不能随意创建了,可能你创建了1分变量内存地址,一经过循环就是一大堆直接内存炸了...
- (void)configIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context {
    __block long indexes = 0;
    while (indexes%10000 == 0) {
        [[CaiXingDataBaseService sharedInstance] queryAllNumberInfo:[NSString stringWithFormat:@"%ld",(long)indexes] result:^(NSArray *numberArr) {
            for (NSDictionary *phone in numberArr) {
                @autoreleasepool {
                    if (phone[@"number"]) {
                        NSString *numberString = phone[@"number"];
                        CXCallDirectoryPhoneNumber shortNumber = numberString.longLongValue;
                        [context addIdentificationEntryWithNextSequentialPhoneNumber:shortNumber label:[NSString stringWithFormat:@"%@",phone[@"name"]]];
                    }
                }
            }
            indexes = indexes + [numberArr count];
        }];
    }
}

- (void)queryAllNumberInfo:(NSString *)number result:(void(^)(NSArray *numberArr))result {
    __block NSString *sql = [NSString stringWithFormat:@"select * from tixingcaiyin order by number limit 10000 offset %@",number];
    __block NSMutableArray *tempArray = [[NSMutableArray alloc] init];
    [self.dbQueue inDatabase:^(FMDatabase *db) {
        FMResultSet *set = [db executeQuery:sql];
        while ([set next]) {
            @autoreleasepool {
                [tempArray addObject:set.resultDictionary];
            }
        }
    }];
    if (result) {
        result((NSArray *)tempArray);
    }
}

调试方法

有些小伙伴和我说断点总是进不去,以下给出调试方法:

前提:1.真机 2.证书配好 3.记得插Sim卡
打开地方:手机的设置->电话->来电组织与身份识别里面打开.

1.运行程序在Attach to Process里面找到你的Callkit然后点击,然后再去设置里面打开就可以了.(不推荐,我找了20多次就出现了2次,浪费时间)

iOS利用Callkit实现来电提醒_第4张图片
image_1bnfaknteqnk2g6400a9g1lo616.png-749.7kB

2.add你的应用名称,然后Attach,这样你的应用第一次打开Callkit时候(不推荐,第一次才行,而且你删了程序再装还是有缓存的要多试几次)

iOS利用Callkit实现来电提醒_第5张图片
image_1bnfaqfdt5571g5g1sade46bvn19.png-150.5kB

iOS利用Callkit实现来电提醒_第6张图片
image_1bnfaqvmdf391la8pcd1cdg1mvu1m.png-49.5kB

//注意:只有第一次才有用,如果你打开过再关闭时没有用的,他有缓存,会直接打开不进断点.

3.在你的程序里面加个按钮手动去刷新.(推荐,不管多少次,一但手动调肯定会进断点).

上代码:


@implementation CaiXingDataBaseService (Author)

- (void)reloadCallKitExtensionDatabase:(NSString *)originDBPath {
    //更新的Block
    void(^reloadExtension)() = ^() {
    
        //IdentifierExtension 为扩展的 Bundle Identifier
        CXCallDirectoryManager *manager = [CXCallDirectoryManager sharedInstance];
        
        //判断Callkit扩展有没有开
        [manager getEnabledStatusForExtensionWithIdentifier:[self getAppIdentifier] completionHandler:^(CXCallDirectoryEnabledStatus enabledStatus, NSError * _Nullable error) {
            
            //开了去刷新
            if (enabledStatus == CXCallDirectoryEnabledStatusEnabled) {
                [manager reloadExtensionWithIdentifier:[self getAppIdentifier] completionHandler:^(NSError * _Nullable error) {
                    if (error == nil) {
                        UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"更新成功" preferredStyle:UIAlertControllerStyleAlert];

                        UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

                        [alert addAction:defaultAction];
                    }else{
                        UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"更新失败" preferredStyle:UIAlertControllerStyleAlert];

                        UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

                        [alert addAction:defaultAction];
                    }
                }];
            }
        }];
    };

    //原始的数据库地址判断是否存在,不存在就不走了
    if (![SXYFileManager fileExistsAtPath:originDBPath]) {
        return;
    }

    //判断App Groups里面有没有数据库,有直接删了
    NSString *destDBPath = [self getDataBasePath];
    if ([SXYFileManager fileExistsAtPath:destDBPath]) {
        [SXYFileManager deleteFileAtPath:destDBPath];
    }

    //将需要写入的数据库放到App Groups里面
    [SXYFileManager copyFileAtPath:originDBPath toPath:destDBPath];
    
    //开始更新
    reloadExtension();
}

多个CallKit

注意点: 1.单个callkit在手机的设置->电话->来电组织与身份识别里面肯定是你App名称,但是如果你是多个Callkit的时候就是App名称+扩展的Display Name(别瞎写,最好是以你个扩展的主要功能为名称).
2.里面的顺序问题:就是出现来电的时候显示的优先顺序,多个Callkit可能出现都有1个号码,这个应用标注是垃圾号码,那个标记是骚扰号码等等,出现号码重复,那么以哪个第一显示呢?就是看你的排序最上面的优先级最高.
3.多个扩展可以同时打开开关去写入,这也是为什么推荐每个扩展对应单独的数据库文件.

其他注意点

1.Callkit大数据量的时候不要频繁更新,不然的话苹果爸爸可能不让你写入那个扩展,就出现了写入失败.(看你的内存如果你写入一直内存平稳,但是就是失败,而且log里面没有出现XXX号码less than XXX号码,大部分原因是这个).
2.Callkit上限数据是200万(反正我没测).
3.数据可以分开写,先写入少量的数据,让用户打开权限时,感觉不到延时,当权限打开后,在写入大量的数据,刷新数据源,用户感觉不到等待的时间。写入几十万的数据需要大概十几秒的时间,在我的思维里面觉得是很正常的,但是在产品眼里这是不可接受的。巴拉巴拉说了一堆,说腾讯啊、360啊人家都打开的很快的。你娘啊!!好吧算你赢.
最后想到一个办法,先写入很少量的数据,然后在写入大量的数据。这样就完美的解决了第一次加载时间慢的问题.
具体的来说就是,第一次请求用户打开权限的时间,先写入很少量的数据,当判断用户已经打开了权限,就写入大量的数据,然后刷新下数据源,这样就是在用户完全没有感知的情况下刷新了数据源。(这条借鉴网友的说出了我们开发的心声啊~~).
4.如果你上架AppStore最好提供一个演示视频,被拒了,说不知道如何使用CallKit(这条也是借鉴网友的).
5.新发现:不是你往里面写入了号码,并且加上86,收到号码的时候都会显示你的标注,一般座机号码和手机号码都是可以看到标注的但是短号,5位号码可以,4位不行,其他都没有试,不然你把110标注出诈骗号码,那用你App的用户就好玩了~~~苹果先回去筛选一次然后再去调用你的App的Callkit.

最后真机的log在Xcode里面是看不到得.


image_1bnfc5ncibcm1cup1s1lb5e26q3j.png-1167.4kB

如果你总是失败,在里面command+f 搜索less than一般是这个原因.

你可能感兴趣的:(iOS利用Callkit实现来电提醒)