1. 从 SIM 卡读取联系人到底难在哪
Android 里读取 SIM 卡联系人,说穿了就是通过ContentResolver去查一个特定的 URI,把游标里的tag(姓名)和number(号码)取出来。听起来三行代码就能搞定,但真正在工程里落地时,坑往往不在查询本身,而在“工具链怎么配、权限怎么给、不同 ROM 的 URI 差异怎么兜底、以及怎么把这类系统级能力接到一个统一的调用通道里”。
这篇聚焦的场景很具体:Android / 嵌入式设备上读取 SIM 卡联系人,同时用 TaoToken 的统一 Key 通道把工具侧(CC Switch、Cline 这类编码助手)配置好,让整个开发过程可复现、可验证。适合两类人:一是做 Android 系统集成、需要操作 SIM 通讯录的工程师;二是用 AI 编码工具辅助写这类系统代码、但被 Key 管理和工具配置卡住的开发者。
核心检索词先摆出来:SIM 卡联系人读取靠的是content://icc/adn这个 URI(注意,很多老资料写的是content://icc/and,这是历史拼写,实际以adn为准,下面会讲怎么兼容)。能做什么?查询、插入、更新、删除 SIM 卡上的联系人条目。适合谁?需要直接读写 SIM 通讯录、又不想依赖第三方 App 的开发者。
我试过在真机上直接跑查询,结果第一次就返回空游标——不是代码错,是权限和 URI 都没对上。所以这篇不会只给你一段查询代码,而是把“配置 → 请求 → 验证 → 排错”整条链路走一遍。
2. TaoToken 统一 Key 通道的前置准备
在写 SIM 卡联系人代码之前,先把工具侧的调用通道理顺。TaoToken 在这里扮演的角色是:给 CC Switch、Cline 这类编码工具提供一个统一的 Key 和 API 入口,你不用在每个工具里分别填不同的密钥,改一处即可。
先拿到 Key。打开控制台创建 API Key:
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console创建完 Key 后,接入文档在这里,配置格式、字段含义都以它为准:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=docAPI 的基础地址是https://taotoken.net/api(这个不加 UTM,直接用于代码里的 base_url)。如果你只是想在写代码时让模型帮你补全 SIM 联系人相关的逻辑,用模型对话入口验证一下模型是否正常响应就够了:
https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=models如果你是要长期用编码助手(比如让 Cline 持续帮你写 Android 系统代码),那更适合走 Coding Plan,额度和管理都更省心:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-planKey 的管理页面统一在 API Keys:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys注意:Key 只创建一次就够,多个工具共用同一个 Key,改 Key 时只改一处,这是统一通道最大的好处。别把 Key 硬编码进提交到仓库的代码里。
3. 可复制的工具配置骨架
这一节给两份可直接抄的配置:一份是 CC Switch 的settings.json骨架,一份是 Cline 的config.toml片段。字段名以你本地工具版本为准,但结构可以直接用。
3.1 CC Switch 的 settings.json
{ "provider": "taotoken", "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key填这里", "model": "claude-sonnet", "timeout": 60, "retry": { "max_attempts": 3, "backoff_ms": 800 }, "tools": { "cline": { "enabled": true, "auto_apply": false } } }几个字段说明:base_url固定用https://taotoken.net/api,不要带路径后缀;timeout给 60 秒,系统级代码补全有时响应偏慢;retry建议保留,网络抖动时自动重试比手动重跑省事。auto_apply设成false,让模型给建议、你来确认,避免它直接改你的 SIM 联系人代码。
3.2 Cline 的 config.toml 片段
[provider] name = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的Key填这里" [model] default = "claude-sonnet" max_tokens = 8192 [workspace] root = "./android-sim-demo" context_files = ["app/src/main/java/**/*.java", "app/src/main/AndroidManifest.xml"]context_files这一项很关键:把 AndroidManifest 和 Java 源码目录纳入上下文,模型在补全 SIM 联系人逻辑时才能看到你的权限声明和包结构,不然它给的代码可能缺READ_CONTACTS声明。
3.3 Android 侧的权限声明
工具配好了,代码侧别漏权限。在AndroidManifest.xml里加:
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" />运行时还要动态申请,Android 6.0 以上不动态申请会直接抛 SecurityException。查询 SIM 联系人属于读通讯录,READ_CONTACTS是必须的。
4. 读取 SIM 联系人的完整实现与验证
现在进入正题。SIM 卡联系人的 URI,标准写法是content://icc/adn。老代码里常见的content://icc/and是拼写遗留,部分 ROM 仍兼容,但新设备建议以adn为主、and作兜底。
4.1 查询联系人
public List<SimContact> querySimContacts(Activity activity) { List<SimContact> result = new ArrayList<>(); Uri uri = Uri.parse("content://icc/adn"); Cursor cursor = null; try { cursor = activity.getContentResolver().query(uri, null, null, null, null); if (cursor == null) { return result; } int nameIdx = cursor.getColumnIndex("tag"); int numberIdx = cursor.getColumnIndex("number"); while (cursor.moveToNext()) { String name = nameIdx >= 0 ? cursor.getString(nameIdx) : ""; String number = numberIdx >= 0 ? cursor.getString(numberIdx) : ""; result.add(new SimContact(name, number)); } } catch (Exception e) { Log.e("SimContact", "query failed: " + e.getMessage()); } finally { if (cursor != null) { cursor.close(); } } return result; }注意列名用的是tag和number,不是People.NAME。老资料里用People.NAME是因为早期 API 的常量映射,现在直接取字符串列名更稳。getColumnIndex返回 -1 时要兜底,否则getString(-1)会崩。
4.2 插入联系人
public Uri insertSimContact(Activity activity, String name, String number) { Uri uri = Uri.parse("content://icc/adn"); ContentValues values = new ContentValues(); values.put("tag", name); values.put("number", number); return activity.getContentResolver().insert(uri, values); }插入返回的 Uri 就是新条目的地址,可以拿它做后续更新或删除。
4.3 更新联系人
public int updateSimContact(Activity activity, String oldName, String oldNumber, String newName, String newNumber) { Uri uri = Uri.parse("content://icc/adn"); ContentValues values = new ContentValues(); values.put("tag", oldName); values.put("number", oldNumber); values.put("newTag", newName); values.put("newNumber", newNumber); return activity.getContentResolver().update(uri, values, null, null); }更新时旧值和新值都要给:tag/number定位旧条目,newTag/newNumber是目标值。返回受影响行数,为 0 说明没匹配到。
4.4 删除联系人
public int deleteSimContact(Activity activity, String name, String number) { Uri uri = Uri.parse("content://icc/adn"); String where = "tag=? AND number=?"; String[] args = new String[]{name, number}; return activity.getContentResolver().delete(uri, where, args); }用参数化where而不是字符串拼接,避免姓名里带单引号导致语句出错。
4.5 验证动作与预期结果
写完别急着跑,按这个顺序验证:
第一步,确认权限已授予。在onCreate里检查checkSelfPermission(READ_CONTACTS),没授予就requestPermissions。
第二步,插一张有联系人的 SIM 卡(或先用插入接口写两条测试数据),调用querySimContacts,预期返回的 List 里能看到你写入的姓名和号码。
第三步,调用insertSimContact("TestA", "10086"),预期返回非空 Uri;再查询,预期多出一条TestA / 10086。
第四步,调用updateSimContact("TestA", "10086", "TestB", "10010"),预期返回 1;再查询,预期TestA变成TestB、号码变成10010。
第五步,调用deleteSimContact("TestB", "10010"),预期返回 1;再查询,预期该条消失。
如果查询一直返回空,先看权限,再看 URI 是不是该用and兜底,最后确认 SIM 卡本身是否真的存了联系人(有些卡是空的)。
5. 本篇常见错误排查
错误一:查询返回 null 游标。多半是权限没给,或者 URI 写错。先确认READ_CONTACTS已动态授予,再把content://icc/adn换成content://icc/and试一次。
错误二:getColumnIndex返回 -1 导致崩溃。不同 ROM 的列名可能不完全一致,务必对 -1 做兜底,取不到就跳过该行,别直接getString。
错误三:插入成功但查询看不到。有些设备 SIM 联系人写入后需要重新插拔或重启才刷新,先确认写入返回的 Uri 非空,再重启设备复测。
错误四:工具侧请求报 401。这是 Key 问题,不是代码问题。去 API Keys 页面确认 Key 有效、没被删,再检查settings.json里api_key有没有多余空格。接入细节对照接入文档。
错误五:Cline 补全的代码缺权限声明。说明context_files没把 AndroidManifest 纳入上下文,补上后重新让模型生成。
错误六:更新返回 0。旧条目的tag/number和实际存储值不完全一致(比如号码带了空格或国家码),先查询打印出真实值,再用真实值去更新。
6. 把通道和代码一起收尾
SIM 卡联系人这套读写逻辑本身不复杂,真正花时间的是权限、URI 兼容和工具链配置。把 TaoToken 的统一 Key 通道配好之后,CC Switch 和 Cline 共用一份 Key,改配置只改一处,写这类系统代码时模型能直接看到你的 Manifest 和源码结构,补全质量会明显好一截。
如果你还在调工具接入,先去 API Keys 页面把 Key 管好,再对着接入文档核对字段;如果只是想验证模型能不能帮你写 SIM 联系人逻辑,用模型对话入口试一轮最快;要是打算长期用编码助手持续写 Android 系统代码,Coding Plan 更合适。代码跑通之后,记得把测试用的 SIM 联系人清掉,别把测试数据留在卡里。