Android 手机号获取实战:TelephonyManager、SmsManager 与权限配置全解析
1. 为什么 Android 获取手机号总是不生效很多刚接触 Android 电话相关开发的朋友第一次写获取手机号的代码时都会遇到同一个困惑明明调用了TelephonyManager.getLine1Number()返回的却是一个空字符串。这不是代码写错了而是 Android 从设计上就没有保证这个接口一定能拿到号码。手机号本质上属于运营商写入 SIM 卡的用户数据是否写入、写入哪个字段完全由运营商决定。国内三大运营商在大部分 SIM 卡上并不会把本机号码写进getLine1Number()能读到的位置所以你会看到网上大量「获取手机号失败」的帖子。真正能稳定拿到号码的场景通常是运营商定制机、部分物联网卡或者用户自己在系统设置里手动填过「本机号码」。那实际项目里怎么办常见有三条路一是直接读TelephonyManager能拿到最好拿不到就走兜底二是通过SmsManager发一条查询短信再监听回执短信解析出号码三是干脆放弃本地读取改用服务端短信验证码登录。这篇就把前两条链路完整走一遍从权限声明、运行时申请到读取代码、短信监听再到用 TaoToken 的 API 通道验证接口调用结果给你一套能直接抄的骨架。适合谁看正在做 Android 端手机号相关功能、被getLine1Number()返回空坑过的开发者想搞清楚READ_PHONE_STATE、READ_SMS、RECEIVE_SMS这几个权限到底怎么配的人以及想把本地号码获取和服务端接口验证串起来落地的同学。2. 前置准备权限、TaoToken Key 与项目配置2.1 权限清单先理清楚Android 获取手机号涉及的权限分两类一类是普通声明即可的一类是危险权限必须运行时申请。权限作用是否运行时申请READ_PHONE_STATE读取设备电话状态、getLine1Number是Android 6.0READ_PHONE_NUMBERSAndroid 8.0 读取本机号码更精确是SEND_SMS发送查询短信是RECEIVE_SMS监听回执短信是READ_SMS读取短信内容部分场景是在AndroidManifest.xml里先静态声明uses-permission android:nameandroid.permission.READ_PHONE_STATE / uses-permission android:nameandroid.permission.READ_PHONE_NUMBERS / uses-permission android:nameandroid.permission.SEND_SMS / uses-permission android:nameandroid.permission.RECEIVE_SMS / uses-permission android:nameandroid.permission.READ_SMS /注意READ_PHONE_NUMBERS在 Android 8.0API 26才引入低版本设备上申请会直接失败代码里要做版本判断。2.2 TaoToken 统一 Key 通道准备本地拿到号码之后通常还要把号码送到服务端做校验或绑定。这时候如果每个接口都单独配一套鉴权调试起来很烦。我习惯用 TaoToken 做统一 Key 通道一个 Key 走所有模型和接口调用验证阶段特别省事。先去控制台创建 API Key控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsoleAPI Key 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys创建好之后把 Key 存到本地local.properties或环境变量里别硬编码进代码。API 基础地址是https://taotoken.net/api这个地址不带任何查询参数直接作为baseUrl用。后面验证接口调用结果时我们会用它发一个请求确认号码上报链路是通的。3. 可复制配置TelephonyManager 读取号码骨架3.1 运行时权限申请先写一个权限申请工具方法把电话相关权限一次性申请private val phonePermissions mutableListOfString().apply { add(Manifest.permission.READ_PHONE_STATE) add(Manifest.permission.SEND_SMS) add(Manifest.permission.RECEIVE_SMS) if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { add(Manifest.permission.READ_PHONE_NUMBERS) } } private fun requestPhonePermissions(activity: Activity, requestCode: Int) { val denied phonePermissions.filter { ContextCompat.checkSelfPermission(activity, it) ! PackageManager.PERMISSION_GRANTED } if (denied.isEmpty()) { onPermissionReady() } else { ActivityCompat.requestPermissions(activity, denied.toTypedArray(), requestCode) } }在onRequestPermissionsResult里判断结果全部授予后再执行读取逻辑。这里有个坑READ_PHONE_NUMBERS和READ_PHONE_STATE是两个独立权限只申请前者在部分机型上依然读不到号码建议两个都申请。3.2 读取本机号码fun readLineNumber(context: Context): String { if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) ! PackageManager.PERMISSION_GRANTED) { return } val tm context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager return try { // getLine1Number 在多数国内 SIM 卡上返回空串 tm.line1Number ?: } catch (e: SecurityException) { Log.e(PhoneNumber, 读取号码被拒绝: ${e.message}) } }实测下来line1Number返回空是常态所以一定要有兜底逻辑。可以顺便读一下simOperatorName和networkOperatorName用来判断当前是哪家运营商方便后面决定发查询短信的号码。fun getCarrierInfo(context: Context): PairString, String { val tm context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager val simName tm.simOperatorName ?: unknown val netName tm.networkOperatorName ?: unknown return simName to netName }3.3 SmsManager 发送查询短信当line1Number为空时走短信查询兜底。不同运营商查询指令不同下面这张表是常见对照运营商查询号码指令内容中国移动10086发送「本机号码」或「501」中国联通10010发送「本机号码」中国电信10000发送「本机号码」发送代码fun sendQuerySms(context: Context, targetNumber: String, content: String) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) ! PackageManager.PERMISSION_GRANTED) { return } val smsManager if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { context.getSystemService(SmsManager::class.java) } else { SmsManager.getDefault() } val sentIntent PendingIntent.getBroadcast( context, 0, Intent(SMS_SENT_ACTION), PendingIntent.FLAG_IMMUTABLE ) smsManager.sendTextMessage(targetNumber, null, content, sentIntent, null) }注意Android 12API 31之后SmsManager.getDefault()被标记为废弃推荐用context.getSystemService(SmsManager::class.java)并且PendingIntent必须带FLAG_IMMUTABLE否则会直接抛异常。4. 验证请求监听回执短信并解析号码4.1 注册短信广播接收器回执短信到达后系统会发android.provider.Telephony.SMS_RECEIVED广播。写一个BroadcastReceiver来接收class SmsReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action ! Telephony.Sms.Intents.SMS_RECEIVED_ACTION) return val messages Telephony.Sms.Intents.getMessagesFromIntent(intent) val sb StringBuilder() var sender for (msg in messages) { sender msg.displayOriginatingAddress ?: sb.append(msg.displayMessageBody ?: ) } Log.d(SmsReceiver, 来自 $sender 的内容: $sb) val number extractPhoneNumber(sb.toString()) if (number.isNotEmpty()) { Log.d(SmsReceiver, 解析到本机号码: $number) // 这里可以把号码上报到服务端 } } }注册接收器Android 13API 33之后动态注册必须显式指定RECEIVER_EXPORTED或RECEIVER_NOT_EXPORTEDval filter IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { context.registerReceiver(SmsReceiver(), filter, Context.RECEIVER_EXPORTED) } else { context.registerReceiver(SmsReceiver(), filter) }4.2 从短信文本里提取 11 位号码回执短信里往往夹杂套餐名称、余额、日期等一堆数字直接取第一个数字大概率是错的。用正则把所有连续数字段抓出来再筛选长度等于 11 且以 1 开头的fun extractPhoneNumber(text: String): String { val regex Regex(\\d{11}) val matches regex.findAll(text) for (m in matches) { val candidate m.value if (candidate.startsWith(1)) { return candidate } } return }这个正则比原版\\d*更稳因为\\d*会匹配空串遍历时容易拿到一堆空结果。限定\\d{11}直接锁定手机号长度再校验首位是 1误判率低很多。4.3 用 TaoToken 接口验证上报结果号码解析出来后一般要上报到服务端。验证阶段可以用 TaoToken 的 API 通道发一个请求确认链路通不通。先看接入文档确认请求格式接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc用 curl 快速验证curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [ {role: user, content: 验证手机号上报链路返回 ok} ] }如果返回正常的 JSON 结构说明 Key 和网络通道都没问题。你也可以在模型对话页面直接手动测一条模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentchat成功返回的样子大概是这样{ id: chatcmpl-xxx, object: chat.completion, choices: [ { index: 0, message: { role: assistant, content: ok } } ] }看到choices数组里有内容就说明整条调用链是通的。这时候再把 Android 端解析出的号码拼进请求体就能完成「本地读取 → 服务端校验」的闭环。5. 本篇常见错排查5.1 getLine1Number 返回空字符串这是最高频的问题前面已经说过根因是运营商没把号码写进 SIM 卡对应字段。排查顺序先确认READ_PHONE_STATE和READ_PHONE_NUMBERS都授予了再确认设备插了 SIM 卡且信号正常最后接受「读不到是正常的」这个事实走短信兜底或服务端验证码方案。不要在这上面死磕浪费时间。5.2 短信广播收不到先检查RECEIVE_SMS权限是否授予。Android 8.0 之后广播是隐式的动态注册时如果没加RECEIVER_EXPORTED在 Android 13 上会直接抛SecurityException。另外部分国产 ROM 有短信权限二次管控需要在系统设置里手动允许应用读取短信。5.3 PendingIntent 报 FLAG_IMMUTABLE 错误Android 12 起所有PendingIntent必须显式指定可变性标志。发送短信时用的sentIntent加上PendingIntent.FLAG_IMMUTABLE即可。如果业务需要接收方修改 Intent 内容才用FLAG_MUTABLE但短信场景基本不需要。5.4 正则匹配到错误号码回执短信里可能包含客服电话、日期、金额等数字。用\\d{11}加首位为 1 的校验能过滤掉绝大部分干扰。如果还是误判可以进一步限定号码前一位不是数字后一位不是数字避免从更长数字串里截取。5.5 TaoToken 请求返回 401先确认Authorization头格式是Bearer 你的Key中间有一个空格。再确认 Key 没有过期或被删除去 API Keys 页面核对一下。如果本地用环境变量传 Key注意 shell 里$TAOTOKEN_API_KEY是否真的被赋值了可以echo一下确认。6. 落地建议与后续接入把上面几块拼起来一个完整的手机号获取流程就是申请权限 → 读line1Number→ 为空则发查询短信 → 监听回执 → 正则解析 → 上报服务端验证。这套链路在大部分 Android 设备上都能跑通读不到号码时也有兜底不会卡死。如果你后续要做更复杂的编码任务比如把号码校验逻辑接到 Agent 工作流里或者需要长期跑批量接口调用可以了解一下 Coding PlanCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan接入文档里对请求参数、返回结构、错误码都有详细说明遇到报错先翻文档比到处搜快得多接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc最后提醒一句getLine1Number能拿到号码是运气拿不到是常态产品设计时别把「自动获取本机号码」当成必选路径留一条手动输入或验证码登录的退路用户体验会稳很多。

相关新闻

用过才敢说!盘点2026年巅峰之作的AI论文工具,TaoToken统一Key接入实测

用过才敢说!盘点2026年巅峰之作的AI论文工具,TaoToken统一Key接入实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/28 18:28:39 阅读更多 →
密码题背后的置换群与循环节:从暴力模拟到O(n)还原

密码题背后的置换群与循环节:从暴力模拟到O(n)还原

如果你在百炼OJ刷题,看到题号2818、题目名“密码”的时候,千万别急着写一个while(k--)就开始暴力模拟。我第一次做这题就是这么交的,WA了两次又TLE了一次,老老实实回来画了一下午置换图才算搞明白。这道题表面是字符串处理&#x…

2026/9/29 18:30:48 阅读更多 →
RAG实战指南:为AI Agent打造知识获取管道

RAG实战指南:为AI Agent打造知识获取管道

做 AI Agent 快两年了,有个场景我印象特别深:用户拿着企业内部一堆散落文档来问——"我们的报销制度,单笔超过多少需要分管副总审批?"你让大模型硬答,它要么编一个数字,要么理直气壮地说"我…

2026/9/29 18:31:30 阅读更多 →

最新新闻

研究想法如何落地:用AI辅助生成研究草案并一键验证

研究想法如何落地:用AI辅助生成研究草案并一键验证

做“深度学习知识追踪与自适应学习”这个方向,我有了大概想法但却不知道怎么把它落地。我想做的简单说就是:让系统根据学生的做题记录判断掌握程度,再推荐下一步学什么。可研究问题怎么定、方法怎么设计、数据从哪来,一样都说不清…

2026/9/29 21:56:47 阅读更多 →
【2026最新】Qwen-Audio-3.1-TTS-Next 实测教程:一次调用生成完整声景,接口调用、时序编排与逐单成本核算(附完整命令)

【2026最新】Qwen-Audio-3.1-TTS-Next 实测教程:一次调用生成完整声景,接口调用、时序编排与逐单成本核算(附完整命令)

9 月 21 日上架的 Qwen-Audio-3.1-TTS-Next,主打的是“一段剧本进、一整场戏出”:人声、音效、环境声在同一次生成里按时序排好。本文把半天实测的 8 笔调用整理成四步走:接口怎么调、时序怎么控、上限在哪、钱怎么算,命令与数字全…

2026/9/29 21:56:47 阅读更多 →
我的编程目标

我的编程目标

我是heyu07,编程想先把C语言学完,然后我加入了我们学校的ACM集训队,所以对算法的要求很高;并且我还报名了11月份的计算机C语言竞赛,所以我希望在11月份就结束C语言,并且后续专注于算法的研究。现在是大学生…

2026/9/29 21:56:46 阅读更多 →
微信开源WeKnora实战:RAG知识库解析、部署与Agent延展

微信开源WeKnora实战:RAG知识库解析、部署与Agent延展

微信团队在GitHub上悄悄放出了一个叫WeKnora的项目,圈内做RAG和Agent方向的开发者几乎是一夜之间开始讨论它。我第一时间把代码拉下来跑了一遍,又翻了翻issue区和几个技术群的讨论,发现很多人对它的定位其实有误解——有人把它当成又一个&quo…

2026/9/29 21:56:46 阅读更多 →
TensorFlow 2024实战指南:从生产部署到TFLite的完整链路

TensorFlow 2024实战指南:从生产部署到TFLite的完整链路

TensorFlow这个老伙计,这些年真是经历了不少风风雨雨。从1.x时代静态图的繁琐,到2.x时代拥抱动态图与Keras的一体化,再到2024年AI框架格局被PyTorch在学术界强势挤压,不少朋友问我:TensorFlow到底还值不值得学&#xf…

2026/9/29 21:56:46 阅读更多 →
智能体基础概念

智能体基础概念

什么是AI智能体? 智能体(agent)是指能够感知环境并采取行动以实现特定目标的代理体。它可以是软件、硬件或一个系统,具备自主性、适应性和交互能力。智能体通过感知环境中的变化(如通过传感器或数据输入)&a…

2026/9/29 21:55:46 阅读更多 →

日新闻

开源模型端侧落地实战:量化、推理加速与Agent上下文管理

开源模型端侧落地实战:量化、推理加速与Agent上下文管理

1. 从"追平"到"端侧落地":开源模型这波到底变了什么如果你最近半年一直在关注模型圈的动态,应该能明显感觉到一个拐点:开源模型和闭源旗舰之间的差距,正在从"代差"变成"身位差"。以前大家…

2026/9/29 0:00:05 阅读更多 →
AI Evals实战指南:从零搭建LLM应用评估体系与CI/CD集成

AI Evals实战指南:从零搭建LLM应用评估体系与CI/CD集成

1. 为什么AI Evals值得你花时间搞明白做LLM应用的人,迟早会撞上同一堵墙:模型输出飘忽不定,今天答得好好的,明天换个问法就胡说八道。你改了一版提示词,感觉好像好了点,但到底好了多少?说不清。…

2026/9/29 0:00:05 阅读更多 →
Java采购管理系统实战:从数据库设计到事务一致性

Java采购管理系统实战:从数据库设计到事务一致性

简介:这是一套面向Java Web初学者与课程设计者的采购管理系统完整源码,采用JSP技术搭建,配合MySQL数据库,用于解决企业采购信息的管理问题,适合作为毕业设计、课程大作业或进销存类项目的参考模板。系统实现了用户登录…

2026/9/29 0:00:05 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 8:16:59 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/29 8:24:48 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/29 3:55:56 阅读更多 →