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

Kotlin中向SQLite存储号码发SMS告警遇非法地址错误排查

问题:碰撞检测App发送SMS告警时触发Invalid destinationAddress错误

开发一款碰撞检测App,检测到碰撞后需要向SQLite数据库存储的手机号码发送SMS告警,但触发碰撞时Logcat报错:

FATAL EXCEPTION: main
Process: com.mainapp.app, PID: 13054
java.lang.IllegalArgumentException: Invalid destinationAddress

手机号码格式为巴基斯坦本地号码(例如03331234567),但仍触发该错误。以下是相关代码:

SensorDataProcessor类代码

package com.mainapp.app.modules.home

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.hardware.Sensor
import android.media.RingtoneManager
import android.os.Build
import android.telephony.SmsManager
import android.util.Log
import androidx.core.app.NotificationCompat
import com.mainapp.app.ml.TfliteModel
import com.mainapp.app.modules.contacts.data.database.ContactDatabaseHelper
import org.tensorflow.lite.DataType
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer

class SensorDataProcessor(private val context: Context) {

    private val model: TfliteModel = TfliteModel.newInstance(context)
    private val contactDatabaseHelper = ContactDatabaseHelper(context)

    fun preprocessDataAndDetectCrash(sensorType: Int, sensorValues: FloatArray) {
        // Preprocess sensor data
        val preprocessedValues = preprocessData(sensorType, sensorValues)

        // Perform inference using TensorFlow Lite model
        val crashProbability = runInference(preprocessedValues)

        // Determine if a crash has occurred based on the inference result
        processInferenceResult(crashProbability)
    }

    private fun preprocessData(sensorType: Int, values: FloatArray): FloatArray {
        val preprocessedValues = when (sensorType) {
            Sensor.TYPE_ACCELEROMETER -> preprocessAccelerometerData(values)
            Sensor.TYPE_GYROSCOPE -> preprocessGyroscopeData(values)
            Sensor.TYPE_GRAVITY -> preprocessGravityData(values)
            Sensor.TYPE_ORIENTATION -> preprocessOrientationData(values)
            Sensor.TYPE_ROTATION_VECTOR -> preprocessRotationVectorData(values)
            Sensor.TYPE_LINEAR_ACCELERATION -> preprocessLinearAccelerationData(values)
            else -> throw IllegalArgumentException("Unsupported sensor type: $sensorType")
        }
        return preprocessedValues
    }

    private fun preprocessAccelerometerData(values: FloatArray): FloatArray {
        // Preprocess accelerometer data
        // Ensure that the input shape matches [1, 21]
        return values.copyOf(21)
    }

    private fun preprocessGyroscopeData(values: FloatArray): FloatArray {
        // Preprocess gyroscope data
        // Ensure that the input shape matches [1, 21]
        return values.copyOf(21)
    }

    private fun preprocessGravityData(values: FloatArray): FloatArray {
        // Preprocess gravity data
        // Ensure that the input shape matches [1, 21]
        return values.copyOf(21)
    }

    private fun preprocessOrientationData(values: FloatArray): FloatArray {
        // Preprocess orientation data
        // Ensure that the input shape matches [1, 21]
        return values.copyOf(21)
    }

    private fun preprocessRotationVectorData(values: FloatArray): FloatArray {
        // Preprocess rotation vector data
        // Ensure that the input shape matches [1, 21]
        return values.copyOf(21)
    }

    private fun preprocessLinearAccelerationData(values: FloatArray): FloatArray {
        // Preprocess linear acceleration data
        // Ensure that the input shape matches [1, 21]
        return values.copyOf(21)
    }

    private fun runInference(inputData: FloatArray): Float {
        // Create inputs for the TensorFlow Lite model
        val inputFeature0 = TensorBuffer.createFixedSize(intArrayOf(1, inputData.size), DataType.FLOAT32)
        inputFeature0.loadArray(inputData)

        // Run model inference
        val outputs = model.process(inputFeature0)

        // Get the output feature (assuming it's a single value)
        val outputFeature0 = outputs.outputFeature0AsTensorBuffer

        // Extract and return the inference result
        return outputFeature0.floatArray[0] // Assuming it's a single output value
    }

    fun processInferenceResult(crashProbability: Float) {
        if (crashProbability > 4.004052544579376544) {
            triggerAlarmAndSendSMS()
        } else {
            Log.d("CrashDetection", "No crash detected. Probability: $crashProbability")
        }
    }

    private fun triggerAlarmAndSendSMS() {
        startAlarm(context)
        sendSMSAlerts()
    }

    private fun sendSMSAlerts() {
        val contacts = contactDatabaseHelper.getAllContacts()

        if (contacts.isNotEmpty()) {
            val smsManager = SmsManager.getDefault()

            for (contact in contacts) {
                val message = "Emergency: A crash has been detected! Please check the user's location."
                smsManager.sendTextMessage(contact.number, null, message, null, null)
                Log.d("SMSAlert", "SMS sent to ${contact.name}")
            }
        } else {
            Log.d("SMSAlert", "No contacts found in the database.")
        }
    }

    private fun triggerAlarmAndPopup() {
        // Start the alarm
        startAlarm(context)

        // Show notification with action to stop alarm
        showNotificationWithAction("Crash Detected")
    }

    private fun showNotificationWithAction(message: String) {
        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val notificationId = 1 // Use a unique ID for the notification
        val channelId = "CrashChannel"
        val channelName = "Crash Channel"

        // Create an intent to stop the alarm
        val stopIntent = Intent(context, StopAlertsReceiver::class.java)
        stopIntent.action = "STOP_ALERTS_ACTION"
        val pendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) // Add FLAG_IMMUTABLE flag

        // Create a notification with action button
        val notification = NotificationCompat.Builder(context, channelId)
            .setContentTitle("Crash Detected")
            .setContentText(message)
            .setSmallIcon(android.R.drawable.ic_dialog_alert)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setContentIntent(pendingIntent)
            .addAction(android.R.drawable.ic_delete, "Stop Alarm", pendingIntent) // Action button to stop alarm
            .build()

        // Create notification channel if necessary
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
            notificationManager.createNotificationChannel(channel)
        }

        // Show the notification
        notificationManager.notify(notificationId, notification)
    }

    fun stopAlarm(context: Context) {
        // Stop alarm sound
        val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
        val ringtone = RingtoneManager.getRingtone(context, alarmSound)
        ringtone.stop()
    }

    private fun startAlarm(context: Context) {
        // Start alarm sound
        val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
        val ringtone = RingtoneManager.getRingtone(context, alarmSound)
        ringtone.play()
    }

    // Release resources when no longer used
    fun close() {
        contactDatabaseHelper.close()
    }
}

ContactDatabaseHelper类代码

package com.mainapp.app.modules.contacts.data.database

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.mainapp.app.modules.contacts.data.model.ContactsModel

class ContactDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(SQL_CREATE_ENTRIES)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // Implement database upgrade logic if needed
    }

    fun getAllContacts(): List<ContactsModel> {
        val contactList = mutableListOf<ContactsModel>()
        val db = this.readableDatabase

        val cursor = db.rawQuery("SELECT * FROM $TABLE_NAME", null)
        cursor.use {
            while (it.moveToNext()) {
                val idIndex = it.getColumnIndex(COLUMN_ID)
                val nameIndex = it.getColumnIndex(COLUMN_NAME)
                val numberIndex = it.getColumnIndex(COLUMN_NUMBER)

                // Check if column index is valid before retrieving data
                val id = if (idIndex >= 0) it.getLong(idIndex) else 0
                val name = if (nameIndex >= 0) it.getString(nameIndex) else ""
                val number = if (numberIndex >= 0) it.getString(numberIndex) else ""

                val contact = ContactsModel(id, name, number)
                contactList.add(contact)
            }
        }
        return contactList
    }

    companion object {
        const val DATABASE_NAME = "contacts.db"
        const val DATABASE_VERSION = 1

        const val TABLE_NAME = "contacts"
        const val COLUMN_ID = "_id"
        const val COLUMN_NAME = "name"
        const val COLUMN_NUMBER = "number"

        private const val SQL_CREATE_ENTRIES = """
            CREATE TABLE $TABLE_NAME (
                $COLUMN_ID INTEGER PRIMARY KEY,
                $COLUMN_NAME TEXT,
                $COLUMN_NUMBER TEXT
            )
        """
    }
}

问题分析与解决方案

Invalid destinationAddress错误表明传入SmsManager的号码不符合格式要求,即使你认为格式正确,可能存在以下问题:

1. 号码为空或无效值

数据库中可能存储了空字符串(比如列不存在时默认返回空),直接传入会触发错误。
解决:在发送SMS前添加号码校验,跳过空值:

private fun sendSMSAlerts() {
    val contacts = contactDatabaseHelper.getAllContacts()

    if (contacts.isNotEmpty()) {
        val smsManager = SmsManager.getDefault()

        for (contact in contacts) {
            val phoneNumber = contact.number.trim()
            if (phoneNumber.isEmpty()) {
                Log.w("SMSAlert", "跳过空号码,联系人:${contact.name}")
                continue
            }
            val message = "Emergency: A crash has been detected! Please check the user's location."
            smsManager.sendTextMessage(phoneNumber, null, message, null, null)
            Log.d("SMSAlert", "SMS sent to ${contact.name}")
        }
    } else {
        Log.d("SMSAlert", "No contacts found in the database.")
    }
}

2. 号码未符合E.164标准

Android的SmsManager要求号码遵循E.164格式(巴基斯坦号码需为+923331234567,而非本地格式03331234567)。
解决:添加号码格式化方法,自动转换为E.164格式:

private fun formatToE164(phoneNumber: String): String {
    // 移除所有非数字字符
    val cleaned = phoneNumber.replace(Regex("[^0-9]"), "")
    return when {
        cleaned.startsWith("0") -> "+92${cleaned.substring(1)}" // 替换本地前缀0为+92
        cleaned.startsWith("92") -> "+$cleaned" // 已有国家代码,添加+号
        else -> cleaned // 保留原格式(如果已经是E.164)
    }
}

然后在发送时使用格式化后的号码:

val formattedNumber = formatToE164(contact.number)
smsManager.sendTextMessage(formattedNumber, null, message, null, null)

3. 权限缺失(辅助检查)

虽然错误不是权限相关,但需确保已申请SEND_SMS权限并获取授权:

  • 在AndroidManifest.xml中添加:
<uses-permission android:name="android.permission.SEND_SMS"/>
  • 在代码中请求运行时权限(针对Android 6.0+)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 09:02:33