CallLog Provider:通话记录的那些事儿

通话记录这个功能,说实话,每个手机都有。但你真的了解它背后的机制吗?

我个人习惯把 CallLog Provider 看作是 Android 系统里最「接地气」的 Content Provider 之一。为什么这么说?因为它直接关系到用户的日常通信行为——谁打过电话、打了多久、是接听还是未接,这些数据都藏在 CallLog 表里。

CallLog Provider 是什么?

说白了,它就是系统提供的一个公开接口,用来读写通话记录。你手机里那个「通话记录」列表,底层就是靠它来支撑的。

我记得有一次做企业通讯录 App,需要把通话记录同步到后台做分析。当时第一反应就是:直接查 CallLog 表不就行了?嗯,确实可以,但要注意权限问题。

核心要点: CallLog Provider 的数据存储在 content://call_log/calls 这个 URI 下。

查询通话日志

查询通话记录,其实就是一个标准的 ContentResolver 查询操作。我直接上代码吧,这样更直观。

// 查询通话记录
ContentResolver resolver = getContentResolver();
Uri uri = CallLog.Calls.CONTENT_URI;

// 我只关心最近 50 条记录
String selection = null;
String[] selectionArgs = null;
String sortOrder = CallLog.Calls.DATE + " DESC LIMIT 50";

Cursor cursor = resolver.query(
    uri,
    null,  // 查询所有列
    selection,
    selectionArgs,
    sortOrder
);

if (cursor != null) {
    while (cursor.moveToNext()) {
        String number = cursor.getString(
            cursor.getColumnIndex(CallLog.Calls.NUMBER));
        long duration = cursor.getLong(
            cursor.getColumnIndex(CallLog.Calls.DURATION));
        int type = cursor.getInt(
            cursor.getColumnIndex(CallLog.Calls.TYPE));
        long date = cursor.getLong(
            cursor.getColumnIndex(CallLog.Calls.DATE));
        
        // 处理数据...
    }
    cursor.close();
}

这里有个坑,我曾经踩过:CallLog.Calls.TYPE 返回的是 int 值,对应关系如下:

类型常量 含义
INCOMING_TYPE 1 来电
OUTGOING_TYPE 2 去电
MISSED_TYPE 3 未接
VOICEMAIL_TYPE 4 语音邮件
REJECTED_TYPE 5 已拒绝
BLOCKED_TYPE 6 已拦截
小技巧: 如果你只需要某一种类型的记录,比如只看未接来电,可以在 selection 里加条件:CallLog.Calls.TYPE + " = ?",参数传 "3"。

插入通话记录

插入通话记录,这个操作稍微敏感一些。为什么?因为系统不希望任何 App 随意篡改通话记录。你想想看,如果某个恶意 App 往你通话记录里插一堆虚假数据,那不乱套了?

不过,在 Android 10(API 29)之前,确实可以通过 ContentResolver 直接插入。我当年做过一个通话录音 App,就用了这个方式把录音标记和通话记录关联起来。

// 插入一条通话记录(Android 10 之前可用)
ContentValues values = new ContentValues();
values.put(CallLog.Calls.NUMBER, "13800138000");
values.put(CallLog.Calls.TYPE, CallLog.Calls.OUTGOING_TYPE);
values.put(CallLog.Calls.DURATION, 120);  // 单位:秒
values.put(CallLog.Calls.DATE, System.currentTimeMillis());
values.put(CallLog.Calls.NEW, 0);  // 0 表示已查看

Uri insertedUri = getContentResolver().insert(
    CallLog.Calls.CONTENT_URI, values);
注意: 从 Android 10 开始,直接插入 CallLog 需要 WRITE_CALL_LOG 权限,并且系统会限制非默认拨号器 App 的写入行为。说白了,普通 App 已经不能随便插了。

权限问题

说到权限,这是 CallLog Provider 里最容易出问题的地方。我见过太多开发者因为权限没处理好导致崩溃。

  • 读取权限: READ_CALL_LOG,需要在 AndroidManifest 中声明,并且是危险权限,需要运行时申请。
  • 写入权限: WRITE_CALL_LOG,同样需要声明和运行时申请。
  • 特殊说明: 如果你的 App 是默认拨号器,可以免去部分限制。但默认拨号器的申请流程比较复杂,不是随便就能拿到的。

我曾经遇到一个 case:用户反馈 App 在 Android 11 上查询通话记录时直接闪退。排查后发现,是运行时权限申请成功后,没有重新查询,导致 cursor 为 null。嗯,这个坑大家要注意——权限申请成功后,最好重新走一遍查询流程。

知识体系结构图

下面这张图,我把 CallLog Provider 的核心知识点串起来了。你看一眼就能明白整体脉络。

CallLog Provider 知识体系 CallLog Provider 查询通话记录 ContentResolver.query() 插入通话记录 ContentResolver.insert() 权限管理 READ/WRITE_CALL_LOG 按类型筛选 INCOMING/OUTGOING 排序与分页 ORDER BY + LIMIT Android 10+ 限制 默认拨号器才可写 运行时申请 动态权限处理 核心 URI:content://call_log/calls 数据列:NUMBER, DURATION, TYPE, DATE 等

避坑指南

最后,我把自己这些年踩过的坑总结一下,希望能帮你少走弯路。

  • Cursor 一定要关闭: 我曾经因为忘记 close cursor,导致内存泄漏,被测试小姐姐追着骂了一周。
  • 权限申请时机: 不要在 onCreate 里直接申请权限,等用户真正需要查看通话记录时再弹窗,体验会好很多。
  • 数据格式: CallLog.Calls.DATE 返回的是毫秒级时间戳,直接拿来用就行。但 CallLog.Calls.DURATION 是秒数,显示时要转成时分秒格式。
  • 空指针处理: 查询结果可能为空,或者某些列不存在(不同厂商 ROM 可能有差异),记得加判空。

好了,关于 CallLog Provider 的核心内容就这些。说白了,它就是一套标准的 Content Provider 操作,只是多了权限和版本限制。你只要把查询、插入、权限这三块搞明白,基本就能应对大部分场景了。


公众号:蓝海资料掘金营,微信deep3321