You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android通话日志兼容问题:部分设备取到倒数第二次通话时长

问题分析与解决方案

核心问题

在部分设备(通话记录默认从下到上排序)上,获取最近一次通话时长时,返回的是倒数第二次通话的数据,而非最新的通话记录。例如测试场景中:拨打客户1(50秒,获取正确)→拨打客户2(2分钟,获取正确)→再次拨打客户1未接通(应返回0秒,但实际返回50秒)。

现有代码的问题点

  1. 依赖不可靠的固定延迟:用delay()等待通话记录写入,但不同设备写入CallLog的时间差异大,可能导致查询时新通话还未被系统持久化。
  2. 错误的cursor移动逻辑:第二个版本中添加的determineCallLogOrder完全多余——查询时已经指定CallLog.Calls.DATE DESC,ContentProvider会严格按照该规则返回结果,和系统通话记录UI的自定义排序无关。这个判断反而导致部分设备上错误调用moveToLast(),取到最旧的记录。
  3. 未处理通话记录写入的异步性:系统写入CallLog是异步操作,通话结束后不会立即完成,固定延迟无法覆盖所有场景。

修正后的解决方案

核心思路

  • 放弃固定延迟,改用重试+时间戳校验确保拿到最新记录;
  • 严格依赖查询时指定的排序规则,直接取cursor第一条记录;
  • 可选通过ContentObserver监听CallLog变化,更高效地触发查询。

完整修正代码

@SuppressLint("Range")
private suspend fun getLatestCallDuration(): String {
    val maxRetries = 5
    var retryCount = 0
    var latestDuration = "0"
    // 记录通话结束时的时间戳(建议在通话结束回调中获取更精准的时间)
    val callEndTimestamp = System.currentTimeMillis()

    while (retryCount < maxRetries) {
        try {
            // 只查询需要的字段,提升性能
            val cursor = contentResolver.query(
                CallLog.Calls.CONTENT_URI,
                arrayOf(CallLog.Calls.DURATION, CallLog.Calls.DATE),
                null,
                null,
                "${CallLog.Calls.DATE} DESC LIMIT 1" // 明确只取最新的一条
            )

            cursor?.use { // use块自动关闭cursor,避免资源泄漏
                if (it.moveToFirst()) {
                    val durationIndex = it.getColumnIndex(CallLog.Calls.DURATION)
                    val dateIndex = it.getColumnIndex(CallLog.Calls.DATE)
                    if (durationIndex != -1 && dateIndex != -1) {
                        val recordTimestamp = it.getLong(dateIndex)
                        // 允许1秒误差,确保记录是本次通话的
                        if (recordTimestamp >= callEndTimestamp - 1000) {
                            latestDuration = it.getString(durationIndex)
                            break // 拿到正确记录,退出重试
                        }
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        retryCount++
        delay(1000) // 每次重试间隔1秒
    }

    return latestDuration
}

// 时长格式化方法保留
private fun secToTime(second: Int): String {
    var sec = second
    val hours = sec / 3600
    val minutes = sec % 3600 / 60
    sec %= 60
    return String.format("%02d:%02d:%02d", hours, minutes, sec)
}

额外优化:监听CallLog变化

注册ContentObserver监听CallLog的写入事件,收到通知后再查询,比重试机制更高效:

private fun registerCallLogObserver() {
    val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
        override fun onChange(selfChange: Boolean) {
            super.onChange(selfChange)
            // 在协程中查询最新记录
            lifecycleScope.launch {
                val duration = getLatestCallDuration()
                // 处理获取到的时长数据
            }
        }
    }
    contentResolver.registerContentObserver(CallLog.Calls.CONTENT_URI, true, observer)
}

注意在页面销毁时注销Observer,避免内存泄漏。

权限校验

确保已申请READ_CALL_LOG权限,Android 13及以上部分厂商还需要POST_NOTIFICATIONS权限。

关键说明

  • 系统CallLog写入是异步的,固定延迟无法适配所有设备,必须通过重试或监听变化确保拿到最新记录;
  • 查询时指定的排序规则由系统ContentProvider处理,和厂商自定义的UI排序无关,直接取第一条即可。

内容的提问来源于stack exchange,提问作者Anand Nishad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 18:54:51