ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

[Android编程]如何操作通话记录:把 CallLog 查询改到 TaoToken 统一 Key 通道

[Android编程]如何操作通话记录:把 CallLog 查询改到 TaoToken 统一 Key 通道 1. 从 CallLog 查询到模型鉴权一个真实踩坑场景Android 里读取通话记录这件事本身并不复杂android.provider.CallLog.Calls这个静态类把字段都摆好了ContentResolver.query一调就能拿到号码、联系人名、通话类型、时间和时长。真正让人头疼的是另一件事当你想在 App 里加一点智能能力比如把最近通话做摘要、识别陌生号码意图、生成回拨提醒你就得接一个模型侧接口。而模型接口的 endpoint 和 Key往往散落在好几个地方——本地local.properties、CI 变量、同事的聊天记录、某个 demo 里硬编码的字符串。工具一多密钥就各自维护改一次要同步五六个文件漏一个就 401。这篇就聚焦这个场景Android 通话记录读取 把模型侧 endpoint 与鉴权统一到 TaoToken 的 Key/API 通道。前半段给你能直接复制的CallLog.Calls查询代码、权限声明和字段解释后半段把模型调用的 Base URL、Key、Model ID 收敛成一份配置避免多工具各管各的密钥。适合正在做通话类工具、号码助手、通话摘要功能的 Android 开发者也适合手上有一堆小工具、被密钥管理搞烦的人。核心检索词先摆出来Android 通话记录读取CallLog.Calls 查询、TaoToken 统一 Key 通道、模型 endpoint 配置。这三件事串起来就是一篇能跟着做的教程。先说清楚边界通话记录属于敏感数据读取需要运行时权限上架审核也会重点看用途说明。本文只讲技术实现和配置方法实际产品里请务必做好隐私合规、最小化采集和用户告知。模型调用部分同理别把通话原文无脑往外发脱敏后再处理是基本操作。我试过把通话记录查询和模型调用混在一个工具类里结果权限回调、Cursor 关闭、网络请求三件事缠在一起调试起来很痛苦。后来拆成两层一层纯查 CallLog 返回数据类一层专门管模型请求配置。下面按这个思路展开。2. TaoToken 前置准备把 endpoint 和 Key 收敛到一处在写代码之前先把模型侧的接入信息准备好。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 这个不加 UTM。你需要拿到两样东西一个 API Key一个要用的 Model ID。拿 Key 的路径很直接进控制台找到 API Keys 页面创建。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 页面是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。创建后复制那串 Key注意它通常只完整显示一次丢了就得重建。为什么强调统一 Key 通道因为 Android 项目里模型调用可能出现在好几个位置App 内的摘要功能、你本地跑的脚本、CI 里的自动化测试、还有你用的编码助手。如果每个地方都配一份 Key轮换时就是灾难。把 Base URL 固定成https://taotoken.net/apiKey 从统一来源注入Model ID 用变量管理这样换模型、换 Key 都只改一处。具体到 Android 工程我建议这样分层构建期注入Key 放local.properties不进版本库通过buildConfigField写进BuildConfig。运行期读取代码里只读BuildConfig.TAO_TOKEN_KEY不出现字面量。endpoint 常量Base URL 和 Model ID 放一个ModelConfig对象全局唯一来源。如果你还用编码助手或命令行工具它们的配置也指向同一个 Base URL 和同一把 Key。比如 Claude Code 这类工具走的是 Anthropic 兼容入口文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Claude Code 的接入说明在 https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这样 App、脚本、助手三边的鉴权口径一致排查问题时不会互相甩锅。有一点要提醒Key 属于凭证别写进AndroidManifest.xml别提交到 Git也别打进 APK 的明文资源里。生产环境更稳妥的做法是走你自己的后端中转App 不直接持有长期 Key。本文为了演示链路用构建期注入的方式你在真实项目里按安全等级调整。3. 可复制配置CallLog 查询代码 endpoint 配置片段这一节给两份能直接用的东西一份是通话记录查询的完整代码一份是模型侧 endpoint 与鉴权的配置片段。3.1 权限声明与运行时申请先在AndroidManifest.xml里声明权限uses-permission android:nameandroid.permission.READ_CALL_LOG / uses-permission android:nameandroid.permission.WRITE_CALL_LOG /只读场景其实READ_CALL_LOG就够WRITE_CALL_LOG是写入才需要。运行时申请Android 6.0 必须private val callLogPermission registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted - if (granted) { loadRecentCalls() } else { // 用户拒绝给出解释入口别反复弹窗 } } fun ensurePermissionAndLoad() { when { ContextCompat.checkSelfPermission( this, Manifest.permission.READ_CALL_LOG ) PackageManager.PERMISSION_GRANTED - loadRecentCalls() else - callLogPermission.launch(Manifest.permission.READ_CALL_LOG) } }3.2 CallLog.Calls 查询代码字段含义先对照一下方便你按需取列字段常量含义类型CallLog.Calls.NUMBER电话号码TEXTCallLog.Calls.CACHED_NAME缓存的联系人名TEXTCallLog.Calls.TYPE通话类型呼入/呼出/未接INTEGERCallLog.Calls.DATE通话发生时间INTEGERCallLog.Calls.DURATION通话时长秒INTEGER查询实现注意 Cursor 一定要关data class CallItem( val number: String?, val name: String?, val type: Int, val date: Long, val duration: Long ) fun queryRecentCalls(context: Context, limit: Int 20): ListCallItem { val projection arrayOf( CallLog.Calls.NUMBER, CallLog.Calls.CACHED_NAME, CallLog.Calls.TYPE, CallLog.Calls.DATE, CallLog.Calls.DURATION ) val result mutableListOfCallItem() context.contentResolver.query( CallLog.Calls.CONTENT_URI, projection, null, null, CallLog.Calls.DEFAULT_SORT_ORDER )?.use { cursor - val idxNumber cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER) val idxName cursor.getColumnIndexOrThrow(CallLog.Calls.CACHED_NAME) val idxType cursor.getColumnIndexOrThrow(CallLog.Calls.TYPE) val idxDate cursor.getColumnIndexOrThrow(CallLog.Calls.DATE) val idxDuration cursor.getColumnIndexOrThrow(CallLog.Calls.DURATION) while (cursor.moveToNext() result.size limit) { result.add( CallItem( number cursor.getString(idxNumber), name cursor.getString(idxName), type cursor.getInt(idxType), date cursor.getLong(idxDate), duration cursor.getLong(idxDuration) ) ) } } return result }CallLog.Calls.DEFAULT_SORT_ORDER默认按时间倒序最近的在最前。类型判断用CallLog.Calls.INCOMING_TYPE、OUTGOING_TYPE、MISSED_TYPE对照即可。3.3 endpoint 与鉴权配置片段把模型侧配置收敛成一份。先看local.properties不进版本库TAO_TOKEN_KEYsk-你的Key TAO_BASE_URLhttps://taotoken.net/api TAO_MODEL_ID你的ModelIDbuild.gradle.kts里注入android { buildFeatures { buildConfig true } defaultConfig { val props java.util.Properties().apply { rootProject.file(local.properties).inputStream().use { load(it) } } buildConfigField(String, TAO_TOKEN_KEY, \${props[TAO_TOKEN_KEY]}\) buildConfigField(String, TAO_BASE_URL, \${props[TAO_BASE_URL]}\) buildConfigField(String, TAO_MODEL_ID, \${props[TAO_MODEL_ID]}\) } }代码里统一读object ModelConfig { val baseUrl: String BuildConfig.TAO_BASE_URL val apiKey: String BuildConfig.TAO_TOKEN_KEY val modelId: String BuildConfig.TAO_MODEL_ID }如果你用命令行工具或编码助手配置也指向同一组值。以 Claude Code 的 Anthropic 兼容配置为例环境变量形式大致是export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的Key具体字段名以官方文档为准文档入口在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这样 App 和助手用的是同一把 Key、同一个 Base URL轮换时只改local.properties和一处环境变量。4. 验证请求一次查询结果与鉴权失败对照配置写完得验证两件事CallLog 查询能不能拿到数据模型侧鉴权能不能通过。4.1 验证 CallLog 查询在 Activity 里调用queryRecentCalls打印前几条val calls queryRecentCalls(this, limit 5) calls.forEach { Log.d(CallLog, number${it.number}, name${it.name}, type${it.type}, date${it.date}, duration${it.duration}) }预期输出类似号码已脱敏number138****0000, name张三, type1, date1730000000000, duration42 number139****1111, namenull, type3, date1729990000000, duration0 number137****2222, name李四, type2, date1729980000000, duration15type1是呼入type2是呼出type3是未接。name为 null 说明通讯录里没匹配到属于正常情况。如果列表为空先确认权限是否真的授予、设备上是否确实有通话记录模拟器通常没有。4.2 验证模型侧鉴权用 curl 打一次最小请求确认 Key 和 Base URL 正确curl -sS https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAO_TOKEN_KEY \ -H Content-Type: application/json \ -d { model: $TAO_MODEL_ID, messages: [{role: user, content: ping}], max_tokens: 16 }成功时返回 JSONchoices数组里有内容。失败时对照下面几种现象可能原因处理HTTP 401Key 错误、过期或没带上检查Authorization头重新在 API Keys 页面确认local proxy failed本地代理配置干扰了请求检查环境变量里的代理设置直连 Base URLreading choices报错响应结构不是预期格式确认请求路径和 Model ID 是否匹配OAuth 相关报错用了不匹配的鉴权方式改用 Bearer Key 方式参考接入文档401 是最常见的。我踩过的坑是local.properties里 Key 带了引号注入后又套了一层引号结果请求头里多了一对服务端直接判无效。排查时把实际发出的请求头打出来看一眼比猜快得多。4.3 把两步串起来验证通过后把通话记录脱敏后喂给模型做摘要链路就通了。注意别把完整号码直接发出去先做掩码处理。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以先用它手动试几条 prompt确认效果再写进代码。5. 本篇常见错排查从 401 到 Cursor 泄漏这一节把实际会撞到的错误集中列一下对照着查。鉴权类401 Unauthorized出现时按顺序查Key 是否复制完整有没有漏字符、带空格、请求头是不是Authorization: Bearer key、Base URL 是不是https://taotoken.net/api而不是别的路径。如果你同时用了编码助手确认它的配置和 App 用的是同一把 Key别一个用旧的、一个用新的。local proxy failed这类报错通常是本地网络环境里有代理设置请求没走到目标地址。检查 shell 里的http_proxy、https_proxy环境变量以及工具自身的代理配置确保请求直连。reading choices或响应解析失败多半是请求路径或 Model ID 不对。确认你调的是/v1/chat/completions这类标准路径Model ID 和控制台里显示的一致。OAuth 相关报错说明鉴权方式用错了。TaoToken 的 API 通道用 Bearer Key不需要走 OAuth 流程按接入文档改过来即可。CallLog 类SecurityException: Permission Denial说明权限没申请或用户拒绝了。运行时权限必须动态申请别只在 Manifest 里声明就以为完事。Cursor 泄漏是另一个高频问题。query返回的 Cursor 必须close()用 Kotlin 的use {}最省心。忘了关长时间运行会报CursorWindowAllocationException或Too many open cursors。查询返回空列表先排除权限再确认设备上真有记录。模拟器默认没有通话记录得手动造几条。配置类BuildConfig字段读不到检查buildFeatures { buildConfig true }有没有开以及local.properties是否被正确加载。字段名拼写、引号转义都要对。Key 进了版本库是最危险的一种。提交前用git status看一眼local.properties应该在.gitignore里。已经提交的赶紧轮换 Key。如果你用 Cline MCP 或 Codex 的auth.json这类配置记住三件套要写全Base URL、Key、Model ID。缺一个都会鉴权失败。Cline MCP 的配置里Base URL 填https://taotoken.net/apiKey 填你的 KeyModel ID 填控制台里的值。Codex 的auth.json同理字段名以官方文档为准。6. 把统一 Key 通道用起来通话记录查询本身是 Android 的基础操作CallLog.Calls把字段都定义好了权限申请加 Cursor 遍历就能拿到数据。真正值得花心思的是模型侧的接入管理把 Base URL 固定成https://taotoken.net/apiKey 从统一来源注入Model ID 用变量管理App、脚本、编码助手三边口径一致。这样轮换 Key 只改一处排查 401 时也不用挨个文件翻。如果你还在用零散的 Key 管理方式建议从这篇的配置片段开始收敛。API Keys 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 管理接入细节看 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期做编码和 Agent 类任务的话Coding Plan 入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以按需了解。最后留一个实用习惯每次改完配置先用 curl 打一次最小请求验证鉴权再跑 App。这样能把配置问题和代码问题分开省掉大量来回调试的时间。通话记录脱敏这件事从第一行代码就做别等上线前才补。
返回列表