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

