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

Android开发:Telephony.Sms.CONTENT_URI无法获取发送失败短信

问题原因

你无法查询到发送失败短信的核心原因是Android系统对非默认短信应用的短信Provider访问做了行级权限限制:

  • 从Android 4.4(API 19)开始,只有系统默认短信应用拥有短信Provider的全量读写权限
  • 非默认短信应用调用SmsManager发送短信时,系统不会直接把短信记录写入公共短信表,而是委托默认短信应用完成写入操作:
    • 发送成功的短信会被写入已发送表,所有持有READ_SMS权限的应用都可以通过Telephony.Sms.CONTENT_URI查询到
    • 发送失败的短信会被标记为MESSAGE_TYPE_FAILED(常量值为5)留在发件箱/失败队列中,这类非终态的短信记录默认只对默认短信应用开放查询权限,第三方非默认应用访问聚合URI时会被系统自动过滤
  • 你当前使用的Telephony.Sms.CONTENT_URI是所有短信文件夹的聚合视图,系统在返回结果前已经过滤掉了你无权限访问的失败/待发送类短信,所以拿不到对应记录。
解决方案

根据需求可选择以下三种落地实现方案:

方案1:遍历查询所有短信子文件夹URI(无需成为默认短信应用,适配大部分机型)

不要只查询聚合URI,单独遍历所有短信分类的Content URI,合并结果后按短信ID去重,部分ROM不会对单独的子文件夹URI做严格的行级过滤:
需要覆盖查询的URI列表:

  • Telephony.Sms.Inbox.CONTENT_URI:收件箱
  • Telephony.Sms.Sent.CONTENT_URI:已发送短信
  • Telephony.Sms.Draft.CONTENT_URI:草稿
  • Telephony.Sms.Outbox.CONTENT_URI:发件箱(含发送中、发送失败暂存的短信)
  • Telephony.Sms.Queued.CONTENT_URI:排队待发送队列
  • 部分机型单独提供的失败短信URI:Uri.parse("content://sms/failed")

替换原有查询逻辑的代码示例:

fun getAllSms(context: Context): MutableList<SmsDto> {
    val reqCols = arrayOf(
        Telephony.Sms._ID, Telephony.Sms.ADDRESS, Telephony.Sms.BODY,
        Telephony.Sms.DATE,
        Telephony.Sms.DATE_SENT,
        Telephony.Sms.TYPE,
        Telephony.Sms.THREAD_ID,
        Telephony.Sms.STATUS,
        Telephony.Sms.READ,
        Telephony.Sms.SEEN,
    )
    val cr: ContentResolver = context.contentResolver
    val messages = mutableListOf<SmsDto>()
    val idSet = mutableSetOf<Long>() // 用于跨表去重

    // 遍历所有需要查询的短信URI
    val queryUris = listOf(
        Telephony.Sms.CONTENT_URI,
        Telephony.Sms.Inbox.CONTENT_URI,
        Telephony.Sms.Sent.CONTENT_URI,
        Telephony.Sms.Draft.CONTENT_URI,
        Telephony.Sms.Outbox.CONTENT_URI,
        Telephony.Sms.Queued.CONTENT_URI,
        Uri.parse("content://sms/failed")
    )

    queryUris.forEach { uri ->
        // use函数会自动关闭Cursor,无需手动调用close()
        cr.query(uri, reqCols, null, null, null)?.use { cursor ->
            while (cursor.moveToNext()) {
                val dto = SmsDto().apply { load(cursor) }
                if (idSet.add(dto.rowId)) { // 同一条短信仅保留一份记录
                    messages.add(dto)
                }
            }
        }
    }

    messages.sortByDescending { it.timestampInMillis }
    return messages
}

注意:该方案在部分管控严格的定制ROM(如最新版MIUI、HarmonyOS)上可能依然无法获取其他应用生成的失败短信,属于系统级权限限制,无公开绕过方法。

方案2:将应用设置为系统默认短信应用(100%获取全量短信)

如果需要完整的短信读写能力,按照Android官方规范实现默认短信应用的必备组件,引导用户将你的应用设为默认短信应用,即可绕过所有行级权限限制,拿到包括失败短信在内的全量短信数据。
默认短信应用核心需要实现的组件包括:

  • 短信发送/接收广播接收器
  • 短信列表、详情展示页面
  • 彩信下载/展示基础能力

方案3:自行维护自研应用发送短信的状态(兼容性最优)

如果你只需要获取自己应用发出的短信的失败状态,不需要读取其他应用的失败短信,可以在调用SmsManager.sendMultipartTextMessage/sendTextMessage时注册发送结果广播PendingIntent,自行监听发送结果:

  • 发送成功时主动将短信写入系统已发送表
  • 发送失败时本地记录失败状态,无需依赖系统短信Provider返回结果,该方案不依赖默认短信应用权限,可适配所有机型。
佐证截图

默认短信应用可读取自研应用发送的失败短信
自研应用无法查询到对应失败短信
自研应用可正常读取收发成功的短信


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 12:57:12