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

如何在Android Kotlin中以编程方式获取被拦截的SMS/MMS消息

问题:获取被拦截联系人的SMS/MMS消息

注意

此功能仅用于侧载测试(实验用途),不面向公开使用

问题描述

在Android设备上已拦截某联系人号码,对方发送消息时系统不会显示,需要获取该拦截状态下的SMS和MMS消息。

已尝试方案

  • 使用AccessibilityService检测拦截联系人发消息时的触发事件,未成功
  • 使用广播接收器,但未接收到任何触发信号

当前使用的MMS获取代码

cursor?.use {
    val uri = Uri.parse("content://mms")
    val projection = arrayOf("_id", "date", "msg_box", "thread_id")
    val selection: String? = null
    val selectionArgs: Array<String>? = null
    val sortOrder: String? = null

    application.applicationContext.contentResolver.query(
        uri,
        projection,
        selection,
        selectionArgs,
        sortOrder
    )
        ?.use { mmsCursor ->
            while (mmsCursor.moveToNext()) {
                val dateIndex = mmsCursor.getLong(mmsCursor.getColumnIndex("date"))
                val epochTimeMilliseconds = dateIndex * 1000
                val formattedMmsSeconds =
                    System.currentTimeMillis() + (epochTimeMilliseconds - System.currentTimeMillis())
                val mmsId = mmsCursor.getString(mmsCursor.getColumnIndex("_id")).toInt()
                val msgBox =
                    mmsCursor.getInt(mmsCursor.getColumnIndexOrThrow("msg_box"))
                val utcDate = Date(dateIndex * 1000L) // convert to milliseconds
                val date = utcDate.toAPIFormat()
                val partUri = Uri.parse("content://mms/$mmsId/part")

                application.applicationContext.contentResolver.query(
                    partUri,
                    null,
                    null,
                    null,
                    null
                )
                    ?.use { partCursor ->
                        while (partCursor.moveToNext()) {
                            val partId =
                                partCursor.getString(partCursor.getColumnIndex("_id"))
                            val type =
                                partCursor.getString(partCursor.getColumnIndex("ct"))
                            val phoneNumber = getSenderAndReceiverNumbers(mmsId)
                            val myPhoneNumber = getPhoneNumber()
                            val messageType: String = getMessageType(msgBox)
                            val contactName = getContactNameFromPhoneNumber(phoneNumber)
                            deviceNumber = myPhoneNumber.replace("[^0-9]".toRegex(), "")
                            val imageUri = Uri.parse("content://mms/part/$partId")
                            val imagePath = getImagePath(imageUri)
                            if (type != null && type.contains("image") && imagePath != null) {

                                viewModelScope.launch {
                                    val bitmap = partId?.let { getMmsImage(it) }
                                    val myIntList = getIdsFromSharedPreferences()

                                    if (shouldProcessMms(
                                            myIntList,
                                            partId,
                                            formattedMmsSeconds,
                                            lastDate
                                        ) && bitmap != null
                                    ) {
                                        viewModelScope.launch(Dispatchers.IO) {
                                            val mmsImageUrl = saveImage(bitmap)
                                            val message: String? =
                                                getMmsText(mmsId.toString())
                                            val messageText = when {
                                                message.isNullOrEmpty() -> ""
                                                result.any { it.mmsId == mmsId } -> ""
                                                else -> message
                                            }
                                            val mmsMessage = MmsMessage(
                                                0,
                                                mmsId,
                                                partId.toInt(),
                                                myPhoneNumber.ifEmpty { noSimCard },
                                                phoneNumber,
                                                contactName.ifEmpty {
                                                    getContactName(messageType)
                                                },
                                                messageText,
                                                date,
                                                messageType,
                                                mmsImageUrl.getRealPath(application.applicationContext),
                                                false,
                                                generateMessageId(),
                                                BuildConfig.VERSION_NAME,
                                                ""
                                            )
                                            result.add(mmsMessage)
                                        }
                                    }
                                    addJsonId(partId)
                                }
                            }
                        }
                    }
            }
        }
}

预期目标

检测到被拦截联系人发来消息的时机,并获取对应的SMS/MMS内容


解决方案建议

1. 监听内容观察者(ContentObserver)

Android的短信/MMS存储在content://sms和content://mms内容提供者中,即使消息被拦截,大部分厂商仍会将消息写入这些数据库。可以注册ContentObserver监听这两个URI的变化,一旦有新消息插入,立即触发处理逻辑:

class SmsMmsObserver(private val handler: Handler, private val callback: () -> Unit) : ContentObserver(handler) {
    override fun onChange(selfChange: Boolean, uri: Uri?) {
        super.onChange(selfChange, uri)
        // 检测到内容变化,回调处理逻辑
        callback()
    }
}

// 注册观察者
val observer = SmsMmsObserver(Handler(Looper.getMainLooper())) {
    // 执行你的消息获取逻辑,比如调用现有代码扫描新消息
}
contentResolver.registerContentObserver(Uri.parse("content://sms"), true, observer)
contentResolver.registerContentObserver(Uri.parse("content://mms"), true, observer)

注意:需要确保应用有READ_SMS权限,Android 11及以上需要QUERY_ALL_PACKAGES权限或者针对短信应用的特定权限。

2. 检查拦截消息的存储位置

部分厂商会将拦截的消息存入单独的数据库(比如系统管家/短信应用的拦截文件夹),可以尝试查询以下常见URI:

  • content://com.android.providers.telephony/sms/blocked
  • 部分厂商自定义URI,比如小米的content://com.miui.securitycenter/blocked/sms
    需要针对目标设备进行适配测试。

3. 优化现有查询逻辑

现有代码只查询了MMS,缺少SMS的处理逻辑,添加SMS查询部分:

// 查询SMS
val smsUri = Uri.parse("content://sms/inbox")
val smsProjection = arrayOf("_id", "address", "date", "body", "type")
contentResolver.query(smsUri, smsProjection, null, null, "date DESC")?.use { smsCursor ->
    while (smsCursor.moveToNext()) {
        val address = smsCursor.getString(smsCursor.getColumnIndex("address"))
        val date = smsCursor.getLong(smsCursor.getColumnIndex("date"))
        val body = smsCursor.getString(smsCursor.getColumnIndex("body"))
        val type = smsCursor.getInt(smsCursor.getColumnIndex("type"))
        // 检查是否是被拦截的联系人,处理消息
    }
}

同时,在查询时可以通过selection参数过滤被拦截的联系人号码,减少不必要的处理:

val blockedNumber = "1234567890"
val selection = "address = ?"
val selectionArgs = arrayOf(blockedNumber)
contentResolver.query(smsUri, smsProjection, selection, selectionArgs, "date DESC")

4. 针对Android 12+的权限适配

Android 12及以上引入了POST_NOTIFICATIONS权限,如果应用没有该权限,可能无法接收广播或触发某些事件。确保在清单文件中声明权限,并动态申请。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 14:42:02