如何在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
相关产品推荐
相关产品推荐

