Android通话日志兼容问题:部分设备取到倒数第二次通话时长
问题分析与解决方案
核心问题
在部分设备(通话记录默认从下到上排序)上,获取最近一次通话时长时,返回的是倒数第二次通话的数据,而非最新的通话记录。例如测试场景中:拨打客户1(50秒,获取正确)→拨打客户2(2分钟,获取正确)→再次拨打客户1未接通(应返回0秒,但实际返回50秒)。
现有代码的问题点
- 依赖不可靠的固定延迟:用
delay()等待通话记录写入,但不同设备写入CallLog的时间差异大,可能导致查询时新通话还未被系统持久化。 - 错误的cursor移动逻辑:第二个版本中添加的
determineCallLogOrder完全多余——查询时已经指定CallLog.Calls.DATE DESC,ContentProvider会严格按照该规则返回结果,和系统通话记录UI的自定义排序无关。这个判断反而导致部分设备上错误调用moveToLast(),取到最旧的记录。 - 未处理通话记录写入的异步性:系统写入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
相关产品推荐
相关产品推荐

